Почему мои потоки иногда "заикаются"?

Я пытаюсь написать многопоточный код для чтения с DAQ-устройства и одновременно отобразить захваченный сигнал:

std::atomic <bool> rendering (false);
auto render = [&rendering, &display, &signal] (void)
    {
        while (not rendering)
            {std::this_thread::yield ();};
        do {display.draw (signal);}
            while (display.rendering ()); // returns false when user quits
        rendering = false;
    };
auto capture = [&rendering, &daq] (void)
    {
        for (int i = daq.read_frequency (); i --> 0;)
            daq.record (); // fill the buffer before displaying the signal
        rendering = true;
        do {daq.record ();} 
            while (rendering);
        daq.stop ();
    };
std::thread rendering_thread (render);
std::thread capturing_thread (capture);

rendering_thread.join ();
capturing_thread.join ();

Иногда это будет работать нормально, но обычно я получаю очень плохое заикание. Я имел render () и capture () печатать строку на каждой итерации цикла, а затем окрашивал строки таким образом, чтобы красный был от render (), а синий - от capture ():

thread execution vs time

Левый участок - от плавного хода, правый участок - от пробега с заиканием.

У меня была примерно эквивалентная программа на C с использованием openMP, и производительность всегда была гладкой:

int status = 0;
#pragma omp parallel num_threads(2) private(tid) shared(status)
/* READ AND DRAW */ {
 tid = omp_get_thread_num ();
 /* DRAW */ if (tid is 0) {
     int finished = 0;
     while (not finished) {
         #pragma omp critical
         /* GET JOB STATUS */ {
             finished = status;
         }
         finished = renderDisplay ();
     }
     #pragma omp critical
     /* TERMINATE DISPLAY */ {
         cvDestroyAllWindows();
     }
     #pragma omp atomic
     status ++;
     #pragma omp flush(status)
 }
 /* READ */ if (tid is 1) {
     int finished = 0;
     while (not finished) {
         #pragma omp critical
         /* GET JOB STATUS */ {
             finished = status;
         }
         captureSignal ();
     }
 }
 #pragma omp barrier
}

По крайней мере, обе версии C и С++ 11 выглядят эквивалентно мне, но я не могу понять, почему заикание происходит в версии С++ 11.

Я не могу опубликовать SSCCE, потому что все подпрограммы daq.* зависят от библиотеки NI DAQ, но, возможно, стоит отметить, что daq.record () блокируется, пока физическое устройство не закончит чтение, и сама библиотека NI DAQ libwns несколько потоков при запуске.

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

Что здесь происходит, и как я могу его контролировать?

update: увеличение частоты выборки DAQ облегчает проблему, что приводит меня к серьезному подозрению, что это имеет какое-то отношение к тому, что daq.record () является блокирующим вызовом.

Ответ 1

Как упоминалось в комментариях, у вас нет большого контроля над планированием. Что, вероятно, может помочь вам еще больше, это отвратиться от спин-замков и условий использования. Это заставит поток визуализации спать, если он пойдет слишком быстро и обработает все данные, полученные потоком захвата. Вы можете посмотреть этот пример для 1-й итерации. В вашем случае каждый раз, когда все данные становятся доступными из потока захвата, вам необходимо вызвать notify_one(). Вы можете использовать версию wait, которая использует только один параметр для вашего случая.

Итак, ваш код станет чем-то вроде этого

std::mutex mutex;
std::condition_variable condition;
std::atomic <bool> rendering (false);
auto render = [&rendering, &display, &signal] (void)
    {
        // this while loop is not needed anymore because
        // we will wait for a signal before doing any drawing
        while (not rendering)
            {std::this_thread::yield ();};
        // first we lock. destructor will unlock for us
        std::unique_lock<std::mutex> lock(mutex);
        do {
               // this will wait until we have been signaled
               condition.wait(lock);
               // maybe check display.rendering() and exit (depending on your req.)
               // process all data available
               display.draw (signal);
           } while (display.rendering ()); // returns false when user quits
        rendering = false;
    };
auto capture = [&rendering, &daq] (void)
    {
        for (int i = daq.read_frequency (); i --> 0;)
            daq.record (); // fill the buffer before displaying the signal
        rendering = true;
        condition.notify_one();
        // special note; you can call notify_one() here with
        // the mutex lock not acquired.
        do {daq.record (); condition.notify_one();} 
            while (rendering);
        daq.stop ();
        // signal one more time as the render thread could have
        // been in "wait()" call
        condition.notify_one();
    };
std::thread rendering_thread (render);
std::thread capturing_thread (capture);

rendering_thread.join ();
capturing_thread.join ();

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