我正在尝试编写一些多线程代码来从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 ()
:
左图是平稳运行,右图是来自口吃的运行。
我使用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
}
至少,C和C ++ 11版本看起来等同于我,但我无法弄清楚为什么在C ++ 11版本中发生口吃。< / p>
我无法发布SSCCE,因为 我尝试在各种配置中实现原子标志并更改函数调用顺序,似乎没有任何效果。 这里发生了什么,我该如何控制它? 更新:提高DAQ的采样率可以缓解这个问题,这让我强烈怀疑这与daq.*
例程都依赖于NI DAQ库,但值得注意的是daq.record ()
阻塞直到物理设备完成读取,并且NI DAQ lib本身在启动时会产生多个线程。daq.record ()
是阻塞调用的事实有关。
答案 0 :(得分:1)
正如评论中的人提到的那样,你对调度没有多少控制权。什么可能可以帮助你更多的是转离旋转锁和使用条件。如果渲染线程过快并且处理了捕获线程产生的所有数据,这将强制将渲染线程置于休眠状态。您可以查看this example一次迭代。在您的情况下,每次从捕获线程获得更多数据时,您需要调用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 ();
这样做也会消耗更少的CPU资源,因为当没有要处理的数据时,渲染线程将进入休眠状态。