为什么我的线程有时会"口吃"?

eve*_*ode 11 c++ multithreading openmp c++11

我正在尝试编写一些多线程代码来从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 ();
Run Code Online (Sandbox Code Playgroud)

有时这会很好,但通常我的口吃很糟糕.我曾render ()capture ()打印在每个循环迭代一条线,然后五色线,以至于红色是render ()蓝是capture ():

线程执行与时间

左图是平稳运行,右图是来自口吃的运行.

我使用openMP大致相当于C语言中的等效程序,性能总是很平滑:

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
}
Run Code Online (Sandbox Code Playgroud)

至少,C和C++ 11版本看起来都与我相同,但我无法弄清楚为什么在C++ 11版本中发生了口吃.

我不能发布SSCCE,因为daq.*例程都依赖于NI DAQ库,但是daq.record ()在物理设备读完之前可能值得注意阻塞,而NI DAQ lib本身在启动时会生成多个线程.

我尝试在各种配置中实现原子标志并更改函数调用顺序,似乎没有任何效果.

这里发生了什么,我该如何控制它?

更新:增加DAQ的采样率可以缓解问题,这使我强烈怀疑这与daq.record ()阻塞调用的事实有关.

ove*_*tic 1

正如评论中的人们提到的那样,您对日程安排没有太多控制权。可能对您更有帮助的是放弃自旋锁和使用条件。如果渲染线程运行得太快并处理了捕获线程生成的所有数据,这将强制渲染线程进入睡眠状态。您可以查看此示例1 次迭代。在您的情况下,每次从捕获线程获得更多数据时,您都需要调用notify_one()。您可以使用针对您的情况仅使用 1 个参数的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 ();
Run Code Online (Sandbox Code Playgroud)

这样做也会消耗更少的 CPU 资源,因为当没有数据要处理时,渲染线程将进入睡眠状态。