我的线程图像生成应用程序如何将其数据传递给gui?
Mandelbrot生成器的慢速多精度实现。 螺纹,使用POSIX线程。 Gtk GUI。
我有点失落。 这是我编写线程程序的第一次尝试。 我实际上并没有尝试转换它的单线程版本,只是试图实现基本框架。
到目前为止它的工作原理的简要说明:
Main创建了watch_render_start线程,该线程等待pthread_cond_signal,当点击“render”按钮时,由GUI回调发出信号。
watch_render_start检查图像是否已经渲染,检查退出等,但如果一切顺利,则会创建render_create_threads线程。
然后render_create_threads线程创建渲染线程,然后使用pthread_join等待它们完成(并使用get_time_of_day进行一些计时 – 在线程中是不是很糟糕?)。
渲染线程的入口点(富有想象力)称为渲染,循环,而next_line计算函数返回TRUE,以便处理更多行。 在这个while循环中,有检查停止或退出。
next_line func在递增变量之前获取要计算的行,以指示下一个要计算的线程的下一行。 如果要处理的线超出图像高度,则返回。 如果没有,则计算该行的内容。 然后递增lines_done并根据图像的高度进行检查,如果> =则返回0,如果<,则返回1。
这里有470多行代码,我相信你会很开心。
#include #include #include #include #include #include #include /* build with: gcc threaded_app.c -o threaded_app -Wall -pedantic -std=gnu99 -lgmp -lmpfr -pthread -D_REENTRANT -ggdb `pkg-config --cflags gtk+-2.0` `pkg-config --libs gtk+-2.0` */ typedef struct { struct timeval tv_start; struct timeval tv_end; } Timer; void timer_start(Timer* t) { gettimeofday(&t->tv_start, 0); } void timer_stop(Timer* t) { gettimeofday(&t->tv_end, 0); } long timer_get_elapsed(Timer* t) { if (t->tv_start.tv_sec == t->tv_end.tv_sec) return t->tv_end.tv_usec - t->tv_start.tv_usec; else return (t->tv_end.tv_sec - t->tv_start.tv_sec) * 1e6 + (t->tv_end.tv_usec - t->tv_start.tv_usec); } #define NTHREADS 8 #define IMG_WIDTH 480 #define IMG_HEIGHT 360 typedef struct { int rc; pthread_t thread; } rthrds; typedef struct { int* arr; int next_line; int lines_done; int rendering; int start; int stop; pthread_t rend[NTHREADS]; int all_quit; int width; int height; double xmin, xmax, ymax; int depth; } image_info; static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data); static void destroy(GtkWidget *widget, gpointer data); void gui_start_render(GtkWidget* widget, gpointer data); void gui_stop_render(GtkWidget* widget, gpointer data); static GtkWidget* gui_pbar = NULL; void *render(void* ptr); int next_line(image_info* img); void* watch_render_start(void* ptr); void* watch_render_stop(void* ptr); void* watch_render_done(void* ptr); void* threads_render_create(void* ptr); pthread_mutex_t next_line_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t lines_done_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t img_start_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t img_stop_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t img_rendering_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t img_start_cond = PTHREAD_COND_INITIALIZER; pthread_cond_t img_stop_cond = PTHREAD_COND_INITIALIZER; pthread_cond_t img_done_cond = PTHREAD_COND_INITIALIZER; pthread_mutex_t all_quit_mutex = PTHREAD_MUTEX_INITIALIZER; int main(int argc, char **argv) { printf("initializing...\n"); image_info* img = malloc(sizeof(image_info)); memset(img, 0, sizeof(image_info)); img->start = 0; img->width = IMG_WIDTH; img->height = IMG_HEIGHT; img->xmin = -0.75509089265046296296296259; img->xmax = -0.75506025752314814814814765; img->ymax = 0.050215494791666666666666005; img->depth = 30000; size_t arr_size = img->width * img->height * sizeof(int); printf("creating array size: %ld bytes\n", arr_size); img->arr = malloc(arr_size); if (!img->arr) { fprintf(stderr, "image dimension too large!\n"); free(img); exit(-1); } memset(img->arr, 0, arr_size); int rc_err; pthread_t thread_start; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); printf("creating watch render start thread...\n"); rc_err = pthread_create(&thread_start, &attr, &watch_render_start, (void*)img); if (rc_err) { fprintf(stderr, "Thread start creation failed: %d\n", rc_err); free(img->arr); free(img); exit(-1); } printf("creating GUI...\n"); GtkWidget *window; GtkWidget *startbutton; GtkWidget *stopbutton; GtkWidget *box1; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (delete_event), NULL); g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL); gtk_container_set_border_width (GTK_CONTAINER (window), 10); box1 = gtk_hbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(window), box1); startbutton = gtk_button_new_with_label ("Start render"); g_signal_connect (G_OBJECT (startbutton), "clicked", G_CALLBACK (gui_start_render), img); gtk_box_pack_start(GTK_BOX(box1), startbutton, TRUE, TRUE, 0); stopbutton = gtk_button_new_with_label ("Stop render"); g_signal_connect (G_OBJECT (stopbutton), "clicked", G_CALLBACK (gui_stop_render), img); gtk_box_pack_start(GTK_BOX(box1), stopbutton, TRUE, TRUE, 0); gui_pbar = gtk_progress_bar_new(); gtk_progress_bar_set_orientation(GTK_PROGRESS_BAR(gui_pbar), GTK_PROGRESS_LEFT_TO_RIGHT); gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(gui_pbar), (gfloat)1.0 ); /* img->real_height); */ gtk_widget_set_size_request(gui_pbar, 75, 0); gtk_box_pack_end(GTK_BOX(box1), gui_pbar, FALSE, FALSE, 0); gtk_widget_show(startbutton); gtk_widget_show(stopbutton); gtk_widget_show(box1); gtk_widget_show(window); printf("starting GUI\n"); gtk_main (); printf("************************\n" "GUI shutdown\n" "************************\n"); printf("setting all_quit\n"); pthread_mutex_lock(&all_quit_mutex); img->all_quit = 1; pthread_mutex_unlock(&all_quit_mutex); printf("signalling watch render start thread to wakeup...\n"); pthread_mutex_lock(&img_start_mutex); pthread_cond_signal(&img_start_cond); pthread_mutex_unlock(&img_start_mutex); printf("waiting for watch render start thread to quit...\n"); pthread_join(thread_start, NULL); printf("done\n"); printf("freeing memory\n"); free(img->arr); free(img); printf("goodbye!\n"); exit(0); } void gui_start_render(GtkWidget* widget, gpointer ptr) { image_info* img = (image_info*)ptr; printf("************\n" "GUI signalling to start render...\n" "************\n"); pthread_mutex_lock(&img_start_mutex); img->start = 1; pthread_cond_signal(&img_start_cond); pthread_mutex_unlock(&img_start_mutex); } void gui_stop_render(GtkWidget* widget, gpointer ptr) { image_info* img = (image_info*)ptr; printf("************\n" "GUI signalling to stop render...\n" "************\n"); pthread_mutex_lock(&img_stop_mutex); img->stop = 1; pthread_mutex_unlock(&img_stop_mutex); } void* watch_render_start(void* ptr) { image_info* img = (image_info*)ptr; int rc_err; pthread_t render_thread; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); int r; int quit = 0; for(;;) { printf("watch_render_start: waiting for img_start_cond\n"); pthread_mutex_lock(&img_start_mutex); if (!img->start) pthread_cond_wait(&img_start_cond, &img_start_mutex); img->start = 0; pthread_mutex_unlock(&img_start_mutex); printf("watch_render_start: recieved img_start_cond\n"); pthread_mutex_lock(&img_rendering_mutex); r = img->rendering; pthread_mutex_unlock(&img_rendering_mutex); printf("checking if we are rendering... "); if (r) { printf("yes\nStopping render...\n"); pthread_mutex_lock(&img_stop_mutex); img->stop = 1; pthread_cond_signal(&img_stop_cond); pthread_mutex_unlock(&img_stop_mutex); pthread_join(render_thread, NULL); printf("render stopped\n"); } else printf("no\n"); pthread_mutex_lock(&all_quit_mutex); quit = img->all_quit; pthread_mutex_unlock(&all_quit_mutex); if (quit) { printf("exiting watch render start thread\n"); pthread_exit(0); } printf("creating render thread...\n"); rc_err = pthread_create(&render_thread, &attr, &threads_render_create, (void*)img); if (rc_err) pthread_exit(0); } } void* threads_render_create(void* ptr) { Timer timing_info; printf("initializing render thread\n"); image_info* img = (image_info*)ptr; pthread_mutex_lock(&img_rendering_mutex); img->rendering = 1; pthread_mutex_unlock(&img_rendering_mutex); pthread_mutex_lock(&lines_done_mutex); img->lines_done = 0; pthread_mutex_unlock(&lines_done_mutex); pthread_mutex_lock(&img_stop_mutex); img->stop = 0; pthread_mutex_unlock(&img_stop_mutex); pthread_mutex_lock(&next_line_mutex); img->next_line = 0; pthread_mutex_unlock(&next_line_mutex); int rc_err, i; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); timer_start(&timing_info); for (i = 0; i rend[i], &attr, &render, (void*)img); if (rc_err) { fprintf(stderr, "\nrender thread #%d creation failed: %d\n", i, rc_err); return 0; } } for (i = 0; i rend[i], NULL); } timer_stop(&timing_info); printf("render-time %.3fs\n\n", timer_get_elapsed(&timing_info) / (double)1e6); printf("all renderer threads finished\n"); pthread_mutex_lock(&img_stop_mutex); img->stop = 0; pthread_mutex_unlock(&img_stop_mutex); pthread_mutex_lock(&img_rendering_mutex); img->rendering = 0; pthread_mutex_unlock(&img_rendering_mutex); printf("at end of threads_render_create\n"); pthread_mutex_lock(&lines_done_mutex); if (img->lines_done >= img->height) printf("image complete\n"); else printf("image interuppted\n"); pthread_mutex_unlock(&lines_done_mutex); pthread_mutex_lock(&img_start_mutex); img->start = 0; pthread_mutex_unlock(&img_start_mutex); printf("exiting render thread\n"); pthread_exit(NULL); } void* render(void* ptr) { image_info* img = (image_info*)ptr; int quit = 0; printf("starting render..\n"); while(next_line(img) && !quit) { pthread_mutex_lock(&img_stop_mutex); quit = img->stop; pthread_mutex_unlock(&img_stop_mutex); pthread_mutex_lock(&all_quit_mutex); quit |= img->all_quit; pthread_mutex_unlock(&all_quit_mutex); } printf("exiting render thread\n"); pthread_exit(0); } int next_line(image_info* img) { int line; pthread_mutex_lock(&next_line_mutex); line = img->next_line++; pthread_mutex_unlock(&next_line_mutex); if (line >= img->height) return 0; int ix,wz; int img_width = img->width; long double x,y,x2,y2,wre=0,wim=0,wre2=0,wim2=0; long double xmin = img->xmin, xmax = img->xmax, ymax = img->ymax; long double xdiff = xmax - xmin; int depth = img->depth; long double c_im = 0, c_re = 0; y = ymax - (xdiff / (long double)img_width) * (long double)line; y2 = y * y; for (ix = 0; ix < img_width; ++ix) { x = ((long double)ix / (long double)img_width) * xdiff + xmin; x2 = x * x; wre = x; wim = y; wre2 = x2; wim2 = y2; for (wz = 0; wz 4.0F) break; } if (wz == depth + 1) wz = 0; img->arr[line * img_width + ix] = wz; } printf("line %d complete\n", line); pthread_mutex_lock(&lines_done_mutex); img->lines_done++; if (img->lines_done == img->height) { pthread_mutex_unlock(&lines_done_mutex); return 0; } pthread_mutex_unlock(&lines_done_mutex); return 1; } static gboolean delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { return FALSE; } static void destroy(GtkWidget *widget, gpointer data) { gtk_main_quit (); }
我已经做到这一点,需要一些关于如何进行的指示。 对于我面临的每一个问题,我只看到一个令人困惑的迷宫解决方案导致死胡同!
我想先解决进度条。 gui需要把锁放在lines_done上。 但是怎么知道什么时候这样做呢? 它多久会看一次lines_done? 我想我可以使用g_idle_add。
然后真正丰富的问题是实际呈现所有这些快乐线程正在生成的数据。 正如在另一个问题上讨论的那样,我将有一个标志数组来指示实际呈现哪些行(因为它们将由于线程和os调度程序的性质而以任意顺序呈现)。 但GUI将如何检查这些? 在与进度条相同的空闲回调中? 并且说正在生成一个8000像素的高大图像,这是8000毫安的锁定和解锁每隔这么多毫秒 – 这需要花费成本吗?
那我该怎么办呢? 这个模型我正在使用,无论它是什么,能够做我想做的事情?
如果您可以访问平台上的primefaces读取和primefaces写入,那么创建一个工作分配表(阅读您的平台的体系结构说明 – 它可能是也可能不是普通的读取和写入都足够好,您可能或可能不需要添加内存障碍):
每行一个字节,最初为零,非零表示该行被分配的线程
…并在每个工作线程的字段中创建一个primefaces更新的行数。 应使用primefaces读/写指令更新和读取表(因此,根据平台上的可用指令,以8,16,32或64位的块为单位)。
顶级逻辑必须确定是否直接在主线程上完成所有工作(如果图像非常小)或者启动一个工作线程,或者启动N个工作线程。
协调线程(或者如果是我可能会废弃协调线程并在主线程上执行此操作)将循环中的一半作业分配给线程(如果少于一定量,则分配所有工作)。 如果它分配的工作少于所有工作,它会监视其他线程并对平均线程和最佳线程的性能进行基准测试。 它确保线程作为一个整体不会耗尽工作,但试图不让线程无所事事。
前端为每个工作者保留一个指针,指向工作者在分配表中所处的位置,当工作人员增加其整数字段表示工作完成了多少行时,前端通过工作分配表向前搜索找到分配给该工作者的作业的行索引,这些作业现在已完成,并更新完成特定行的位缓冲区,并更新总的完成字段。
–
这是一个动态分配工作到线程的通用算法,正如另一张海报建议你可以通过使工作线程应该处理工作线程数和工作线程数的函数的行号静态分配工作,然后将每个工作人员通过primefaces场完成的行数计算到前端。
减少互斥量的数量: –
-
有一个互斥锁用于访问已完成信号的位缓冲区(8000 / 8bits = 1000byte缓冲区)。
-
第二个临时位缓冲区。
-
工作线程锁定互斥锁,在第一个缓冲区中设置位并解锁互斥锁。
-
主循环锁定互斥锁,将第一个缓冲区复制到第二个缓冲区并解锁互斥锁。
-
然后扫描第二个缓冲区为非零,并为每个设置位将该行的数据复制到输出/屏幕。
-
为了减少第一位缓冲区的争用,您可以将第一个位缓冲区划分为8个或甚至16个段(根据行号mod 8或mod 16查看我们的段),并为每个段分配一个互斥锁。
–
可能的方法是使用我建议的设计但是“try_lock”(而不是等待)锁,做几个NOP并重试直到它们变得可用而不是屈服。 可能值得直接使用atomic inc / dec而不是pthread互斥体以获得更高的性能。
最后,除非你有8个处理器,否则不值得拥有8个线程,而且我不知道get_time_of_day。
编辑 :我可能存在一个缺陷,如果主线程被锁定了一点缓冲互斥锁,那么其他线程会浪费大量时间。 通过降低其他线程的优先级可以减少发生这种情况的频率,但我认为更好的整体策略是使用8000个atomic_t类型的数组和primefacesinc / dec指令来表示从工作线程到主线程的行完成。 主线程可以搜索这8000个atomic_t。 我也假设你将工作线程数减少到比CPU数少一个。
编辑 :八个线程似乎有点武断。 你从哪里得到这个号码? 显然,您至少需要一个工作线程。
编辑 :更快的是使用atomic_set_mask来设置前端在循环中扫描的1000字节缓冲区中的位。
编辑 :假设您的平台上有atomic_set_mask。
使用条件变量和next_line_mutex。 渲染到GUI函数可以保持变量与它渲染的最后一行,并在条件触发时将其与next_line变量进行比较,以便它可以看到它需要渲染的行。 next_line函数可以触发条件。
正如我所指出的,上面的条件变量会导致GUI锁定,所以这不是一个好主意。 相反,我认为GUI应该在一个时间间隔内检查行变量,也许每秒一次。
如果8000锁定/解锁操作的性能太慢,那么我建议分批执行3,5,7或甚至8(8个线程)。 如果为每个线程分配不同数量的线来处理,并且每条线占用大约相同的处理时间,则锁定更有可能在采用时无争用。 无争用的锁非常便宜,虽然仍然比普通的CPU操作更昂贵(它必须从使用它的最后一个CPU拉出缓存线)。 通过使next_line成为next_lines(img,8)可以很容易地做到这一点