Как мое многопоточное приложение для создания изображений может передать свои данные графическому интерфейсу?

Медленная реализация генератора Мандельброта с множественной точностью. Многопоточный, с использованием потоков POSIX. ГТК графический интерфейс.

Я немного потерялся. Это моя первая попытка написать многопоточную программу. На самом деле я еще не пытаюсь конвертировать однопоточную версию, просто пытаюсь реализовать базовую структуру.

Краткое описание того, как это работает на данный момент:

Main создает поток watch_render_start, который ожидает pthread_cond_signal, о чем сигнализирует обратный вызов графического интерфейса при нажатии кнопки «рендеринг».

watch_render_start проверяет, рендерится ли уже изображение, проверяет выход и т. д., но если все идет хорошо, он создает поток render_create_threads.

Затем поток render_create_threads создает потоки рендеринга, а затем использует pthread_join, чтобы дождаться их завершения (и выполняет некоторые временные действия с помощью get_time_of_day — разве это плохо в потоках?).

Точка входа потоков рендеринга (воображаемо) называемая рендерингом, зацикливается, в то время как функция вычисления next_line возвращает TRUE для обработки большего количества строк. в этом цикле while есть проверки на остановку или выход.

Функция next_line получает строку, которую она должна вычислить, прежде чем увеличивать переменную, чтобы указать следующую строку для следующего потока для вычисления. Он возвращается, если строка, которую он должен обработать, выходит за пределы высоты изображения. Если нет, то он вычисляет содержимое строки. Затем увеличивает значение lines_done, сравнивает его с высотой изображения и возвращает 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 ();
}

Я зашел так далеко, и мне нужны некоторые указания о том, как действовать дальше. Для каждой проблемы, с которой я сталкиваюсь, я вижу только запутанный лабиринт решения, ведущий в тупик!

Сначала я хотел заняться индикатором выполнения. Графическому интерфейсу нужно будет поставить блокировки на lines_done. Но как узнать, когда это делать? Как часто он будет смотреть на lines_done? Думаю, для этого я мог бы использовать g_idle_add.

Затем возникает реальная серьезная проблема фактического рендеринга данных, которые генерируют все эти счастливые потоки. Как обсуждалось в другом вопросе, у меня будет массив флагов, чтобы указать, какие строки на самом деле отображаются (поскольку они будут отображаться в произвольном порядке из-за характера многопоточности и планировщиков ОС). Но как GUI их проверит? В том же простом обратном вызове, что и индикатор выполнения? И скажем, создается большое изображение высотой 8000 пикселей, которое блокирует и разблокирует 8000 мьютексов каждые несколько миллисекунд — это должно стоить, верно?

Итак, как я должен действовать здесь? Способна ли эта модель, которую я использую, что бы это ни было, делать то, что я хочу?


person James Morris    schedule 10.01.2010    source источник
comment
Для какой аппаратной платформы это? ПК? Все платформы Linux, кроме ПК? Какая-то конкретная рабочая станция?   -  person martinr    schedule 10.01.2010
comment
Я разрабатываю его на Linux x86_64. У меня нет доступа к другим платформам. Надеюсь, он тоже должен работать на Linux x86.   -  person James Morris    schedule 10.01.2010
comment
Понимание многопоточности очень сложно. Как я обычно пытаюсь об этом думать, я думаю об одной модели гипотезы о том, какие потоки обработки требуются, затем я представляю один поток в действии, идентифицирую и фиксирую (определяю) граничные условия для его интеграции с другими (другими потоки). Многократно продумывая все потоки по очереди (и меняя потоки обработки, которые, как я полагаю, необходимы в зависимости от проблем, с которыми я сталкиваюсь), я в конечном итоге чувствую, что определил достаточно точек проектирования, чтобы начать работу.   -  person martinr    schedule 10.01.2010
comment
Обычно я стараюсь основывать свои проекты на понимании поведения инструкций микропроцессора, дизайна кэш-памяти и API-интерфейсов потоковой передачи на конкретной платформе. Они проявляются в виде атомарных инструкций, барьеров памяти, примитивов синхронизации (например, мьютекс, семафор) и понимания того, что может произойти, если планировщик прервет выполнение кода (прервано) в произвольной точке, когда я работаю на уровне языка. Часто существуют определенные приемы и приемы или предпочтительные методы для конкретных платформ.   -  person martinr    schedule 10.01.2010


Ответы (3)


Если у вас есть доступ к атомарному чтению и атомарной записи на вашей платформе (платформах), создайте таблицу распределения работы (прочитайте примечания по архитектуре для ваших платформ — может быть, а может и не быть, что обычные операции чтения и записи достаточно хороши, вы можете или можете не нужно добавлять барьеры памяти):

Один байт на строку, изначально нулевой, ненулевой означает поток, которому выделена строка.

... и создайте атомарно обновляемое количество строк, выполненных в поле int для каждого рабочего потока. Таблица должна быть обновлена ​​и прочитана с использованием атомарных инструкций чтения/записи (таким образом, порциями по 8, 16, 32 или 64 бита в зависимости от доступных инструкций на платформе).

Логика верхнего уровня должна решить, выполнять ли всю работу сразу в основном потоке (если изображение действительно маленькое), или запускать один рабочий поток, или запускать N рабочих потоков.

Поток координации (или, если бы это был я, вероятно, я бы отказался от потока координации и сделал бы это в основном потоке) распределяет половину заданий по кругу между потоками (или всю работу, если их меньше определенного количества). Если он выделяет меньше всей работы, он отслеживает другие потоки и оценивает производительность среднего потока и лучшего потока. Он следит за тем, чтобы нити в целом не остались без работы, но старается не оставлять нити без дела.

Внешний интерфейс хранит указатель для каждого рабочего процесса на то, где в таблице распределения рабочий процесс выполнился, и когда рабочий увеличивает свое целочисленное поле для того, сколько строк он выполнил, внешний интерфейс выполняет поиск вперед по таблице распределения рабочего процесса. находит индексы строк заданий, выделенных этому рабочему, которые в настоящее время завершены, и обновляет битовый буфер, в котором выполняются определенные строки, а также обновляет общее выполненное поле.

--

Это общий алгоритм динамического распределения работы по потокам, поскольку другой автор предложил, что вы можете альтернативно статически распределить работу, сделав номера строк, которые должен обрабатывать рабочий поток, функцией количества рабочих потоков и номера рабочего потока, затем просто передайте количество строк, выполненных каждым работником, через атомное поле во внешний интерфейс.

person martinr    schedule 10.01.2010
comment
Спасибо martinr за усилия, которые вы приложили к этим ответам. Хотя я не понимаю их так хорошо, как я надеялся, они дали мне несколько идей, чтобы попробовать. - person James Morris; 10.01.2010
comment
Маловероятно, что моя реализация будет принимать какие-либо решения относительно объема работы, особенно в зависимости от размера изображения. Хотя размер изображения влияет на время рендеринга, мельчайшие размеры отображаемой области, количество вычисляемых итераций (на пиксель) и общая сложность детализации этой области также сильно влияют на то, сколько времени тратится на рендеринг, так что даже для небольшого изображения размером 200 x 180 время рендеринга можно сократить, используя более одного потока. - person James Morris; 10.01.2010

Чтобы уменьшить количество мьютексов: -

  • Иметь один мьютекс для доступа к битовому буферу строк, сигнализируемых как выполненные (8000/8 бит = 1000-байтовый буфер).

  • Второй временный битовый буфер.

  • Рабочий поток блокирует мьютекс, устанавливает бит в первом буфере и разблокирует мьютекс.

  • Основной цикл блокирует мьютекс, копирует первый буфер во второй и разблокирует мьютекс.

  • Затем сканирует второй буфер на наличие ненулевого значения и для каждого установленного бита копирует данные для этой строки на вывод/экран.

  • Чтобы уменьшить конкуренцию за первый битовый буфер, вы можете разделить первый битовый буфер на 8 или даже 16 сегментов (какой сегмент искать в нас, исходя из номера строки по модулю 8 или по модулю 16) и иметь мьютекс для каждого сегмента.

--

Вероятно, выход состоит в том, чтобы использовать дизайн, который я предложил, но «try_lock» (а не ждать) блокировки, сделать пару NOP и повторять попытку, пока они не станут доступными, а не уступать. Возможно, стоит использовать atomic inc/dec напрямую, а не мьютексы pthread для повышения производительности.

Наконец, не стоит иметь 8 потоков, если у вас нет 8 процессоров, и я не знаю о get_time_of_day.

Редактировать: возможно, есть недостаток в том, что я предполагаю, что если основной поток вытесняется, когда он заблокировал мьютекс битового буфера, другие потоки тратят впустую массу времени. Частота этого может быть уменьшена за счет снижения приоритета других потоков, но я думаю, что лучшая общая стратегия состоит в том, чтобы использовать массив из 8000 типов atomic_t с инструкциями atomic inc/dec, чтобы сигнализировать о завершении строки от рабочих потоков к основному потоку. Эти 8000 atomic_t могут быть найдены основным потоком. Я также предполагал, что вы уменьшите количество рабочих потоков на один меньше, чем количество процессоров.

Изменить: восемь потоков кажутся несколько произвольными. Откуда вы взяли этот номер? Очевидно, вам нужен хотя бы один рабочий поток.

Редактировать. Еще быстрее было бы использовать atomic_set_mask для установки битов в 1000-байтовом буфере, который внешний интерфейс сканирует в цикле.

Изменить: предполагается, что на вашей платформе установлена ​​маска atomic_set_mask.

person martinr    schedule 10.01.2010
comment
Да выбор 8 потоков произволен. До того, как я на самом деле разместил расчет M-set, у меня был гораздо более простой расчет, который работал лучше с 32 потоками, чем с 8. Я запускаю его на двухъядерной системе. IIRC учебники, которые я прочитал, предложили использовать больше потоков, чем ядер/ЦП. - person James Morris; 10.01.2010

Используйте переменную условия вместе с вашим next_line_mutex. Функция рендеринга в GUI может хранить переменную с последней отображаемой строкой и сравнивать ее с переменной next_line всякий раз, когда срабатывает условие, чтобы видеть, какие строки ей нужно отобразить. Функция next_line может запускать условие.

Как мне было сказано, приведенная выше условная переменная приведет к блокировке графического интерфейса, так что это не очень хорошая идея. Вместо этого я полагаю, что графический интерфейс должен проверять переменную строк через временной интервал, возможно, раз в секунду.

Если производительность 8000 операций блокировки/разблокировки слишком низкая, то я бы рекомендовал делать строки пакетами по 3, 5, 7 или даже 8 (для 8 потоков). Если вы назначаете каждому потоку разное количество строк для обработки, и каждая строка занимает примерно одинаковое количество времени обработки, то, скорее всего, блокировка не будет оспариваться, когда она будет взята. Неоспариваемые блокировки очень дешевы, хотя все же дороже, чем обычная операция ЦП (она должна извлекать строку кэша из последнего ЦП, который ее использовал). Это было бы легко сделать, сделав next_line равным next_lines(img, 8)

person Zan Lynx    schedule 10.01.2010
comment
Если я правильно вас понял, графический интерфейс будет ожидать, пока переменная pthread_cond сигнализирует о том, что строка отрисована. Не заблокирует ли это графический интерфейс? - person James Morris; 10.01.2010