代码之家  ›  专栏  ›  技术社区  ›  James Morris

我的线程图像生成应用程序如何将其数据传输到GUI?

  •  2
  • James Morris  · 技术社区  · 15 年前

    Mandelbrot生成器的缓慢多精度实现。线程,使用POSIX线程。GTK GUI。

    我有点迷路了。这是我第一次尝试编写线程程序。实际上,我还没有尝试转换它的单线程版本,只是尝试实现基本框架。

    简要介绍目前它的工作原理:

    main创建watch render_start线程,该线程等待pthread_cond_信号,当单击“render”按钮时,该信号由GUI回调发出。

    观察“渲染”开始检查图像是否已经渲染,检查是否退出等,但如果一切正常,它将创建“渲染”创建“线程”线程。

    渲染“创建线程”线程然后创建渲染线程,然后使用pthread-join等待它们完成(并且使用get-time-of-day做一些计时工作-线程中有那么糟糕吗?).

    渲染线程(想象地)的入口点称为render,循环,而下一行计算func返回true以处理更多的行。在这个while循环中,会检查stop或quit。

    下一行func得到它要计算的行,然后递增变量以指示下一个线程要计算的下一行。如果要处理的线条超出图像高度,则返回。如果没有,则计算行的内容。然后增加行数“完成”,并根据图像高度检查,如果>=则返回0,如果<,则返回1。

    这是470多行代码,我相信您一定会很高兴看到它。

    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    #include <mpfr.h>
    #include <string.h>
    #include <gtk/gtk.h>
    #include <sys/time.h>
    
    /* 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 < NTHREADS; ++i)
        {
            printf("creating renderer thread #%d...\n", i);
            rc_err = pthread_create(&img->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 < NTHREADS; ++i)
        {
            printf("joining renderer thread #%d...\n", i);
            pthread_join(img->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 < depth; ++wz)
            {
                wim = 2.0 * wre * wim + c_im;
                wre = wre2 - wim2 + c_re;
                wim2 = wim * wim;
                wre2 = wre * wre;
                if (wim2 + wre2 > 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 ();
    }
    

    我已经走了这么远了,需要一些关于如何进行的指导。对于我面临的每一个问题,我只看到了一个令人困惑的迷宫式的解决方案,导致了一个死胡同!

    我想先解决进度问题。图形用户界面需要在线路上加锁。但如何知道何时做这件事呢?它会多久看一次线条?我想我可以用“空闲添加”来做这个。

    然后是真正复杂的问题,即实际呈现所有这些快乐线程正在生成的数据。正如在另一个问题上讨论的那样,我将有一个标志数组来指示实际呈现的行(因为由于线程和OS调度程序的性质,它们将以任意顺序呈现)。但是GUI将如何检查这些呢?在与进度条相同的空闲回调中?假设一个8000像素高的大图像正在生成,那就是8000个互斥锁,每这么多毫秒解锁一次-那一定要花很多钱,对吗?

    那么,我该如何在这里进行呢?我使用的这个模型,不管它是什么,能做我想要的吗?

    3 回复  |  直到 15 年前
        1
  •  0
  •   martinr    15 年前

    如果您可以访问平台上的原子读取和原子写入,则创建一个工作分配表(阅读平台的体系结构说明-可能是或可能不是普通读取和写入足够好,您可能需要或可能不需要添加内存屏障):

    每行一个字节,最初为零,非零表示分配给该行的线程

    …并为每个工作线程创建一个原子更新的行计数。应使用原子读/写指令更新和读取表(因此,根据平台上可用的指令,分为8、16、32或64位)。

    顶层逻辑必须计算出是直接在主线程上完成所有工作(如果图像真的很小),还是启动一个工作线程,或者启动n个工作线程。

    协调线程(或者如果是我,我可能会废弃协调线程并在主线程上执行此操作)将半个循环工作分配给线程(如果少于一定数量,则分配所有工作)。如果它分配的工作少于所有的工作,那么它将监视其他线程,并对平均线程和最佳线程的性能进行基准测试。它确保线程作为一个整体不会耗尽工作,但尽量不要让线程无所事事。

    前端为每个工作人员保留一个指针,指向工作人员在分配表中完成的位置,当工作人员增加其已完成的行数的整数字段时,前端将通过工作分配表向前搜索,找到分配给该工作人员的作业的行索引(现在已完成),并更新位缓冲区,其中的特定行完成,也更新一个总完成字段。

    --

    这是一个动态地将工作分配到线程的通用算法,正如另一个海报所建议的,您可以通过创建行号来静态地分配工作,工作线程应该处理工作线程数和工作线程数的函数,然后只传递每个工作线程完成的行数。通过一个原子场到达前端。

        2
  •  0
  •   martinr    15 年前

    要减少互斥数,请执行以下操作:

    • 有一个互斥体,用于访问已完成信号传输的行的位缓冲区(8000/8位=1000字节缓冲区)。

    • 第二个临时位缓冲区。

    • 工作线程锁定互斥体,在第一个缓冲区中设置位并解锁互斥体。

    • 主循环锁定互斥体,将第一个缓冲区复制到第二个缓冲区,并解锁互斥体。

    • 然后扫描第二个缓冲区中的非零位,对于每个设置位,将该行的数据复制到输出/屏幕。

    • 为了减少对第一个位缓冲区的争用,您可以将第一个位缓冲区划分为8个甚至16个段(根据行号mod 8或mod 16在我们中查找哪个段),并为每个段设置互斥。

    ——

    可能要做的是使用我建议的设计,但是“尝试锁”(而不是等待锁),做几个nop,然后重试,直到它们变为可用而不是屈服。为了获得更高的性能,直接使用Atomic inc/dec而不是pthread mutex可能是值得的。

    最后,除非你有8个处理器,否则拥有8个线程是不值得的,而且我不知道如何获得一天的时间。

    编辑 :我建议,如果主线程在锁定了位缓冲互斥体的同时被抢占,那么其他线程可能会浪费大量时间。通过降低其他线程的优先级,这种情况发生的频率可能会降低,但我认为更好的总体策略是使用一个8000个原子类型的数组和原子inc/dec指令,以从工作线程到主线程的信号线完成。这8000个原子能被主线程搜索。我也假设将工作线程的数量减少到比CPU数量少一个。

    编辑 :八个线程似乎有点随意。你从哪里得到这个号码的?显然,您至少需要一个工作线程。

    编辑 :更快的方法是使用原子掩模设置1000字节缓冲区中的位,前端在循环中扫描该缓冲区。

    编辑 :假设您在平台上设置了原子掩模。

        3
  •  0
  •   Zan Lynx    15 年前

    将条件变量与下一个互斥体一起使用。render to gui函数可以保留一个变量与它所呈现的最后一行,并在条件触发时将其与下一行变量进行比较,以便它可以看到需要呈现的行。下一行函数可以触发该条件。

    正如我所指出的,上述条件变量将导致GUI锁定,因此这不是一个好主意。相反,我认为GUI应该在一个时间间隔内检查lines变量,可能是每秒检查一次。

    如果8000锁定/解锁操作的执行速度太慢,那么我建议批量执行3、5、7甚至8行(针对8个线程)。如果为每个线程分配不同数量的要处理的行,并且每一行所花费的处理时间大致相同,那么在获取锁时,锁更有可能是未争用的。非扩展锁是非常便宜的,尽管仍然比正常的CPU操作更昂贵(它必须从使用它的最后一个CPU拉缓存线)。这很容易做到,让下一行成为下一行(img,8)