C++ 为什么我的线程有时会;口吃;?
我正在尝试编写一些多线程代码,以便从DAQ设备读取数据,同时呈现捕获的信号:C++ 为什么我的线程有时会;口吃;?,c++,multithreading,c++11,openmp,C++,Multithreading,C++11,Openmp,我正在尝试编写一些多线程代码,以便从DAQ设备读取数据,同时呈现捕获的信号: std::atomic <bool> rendering (false); auto render = [&rendering, &display, &signal] (void) { while (not rendering) {std::this_thread::yield ();}; do {display.dra
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 ();
至少,C和C++11两个版本看起来都和我一样,但我不明白为什么C++11版本会出现口吃
我无法发布SSCCE,因为daq.*
例程都依赖于NI daq库,但值得注意的是,daq.record()
会一直阻塞,直到物理设备完成读取,而NI daq库本身在启动时会生成多个线程
我尝试过在各种配置中实现原子标志,并更改函数调用顺序,但似乎没有任何效果
这里发生了什么,我如何控制它
更新:增加DAQ的采样率可以缓解这个问题,这让我强烈怀疑这与
DAQ.record()
是一个阻塞调用这一事实有关。正如评论中提到的,您对调度没有太多控制权。可能更能帮助您的是远离旋转锁和使用条件。如果渲染线程运行过快并处理了捕获线程生成的所有数据,则这将强制使其进入睡眠状态。您可以查看1次迭代。在您的情况下,每次从捕获线程获得更多数据时,您都需要调用notify_one()。对于您的案例,您可以使用仅接受1个参数的版本
所以你的代码会变成这样
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 ();
std::mutex mutex;
std::条件\可变条件;
std::原子渲染(false);
自动渲染=[&渲染、显示和信号](无效)
{
//不再需要这个while循环,因为
//我们将在绘制任何图形之前等待信号
while(不渲染)
{std::this_thread::yield();};
//首先我们锁定。析构函数将为我们解锁
std::唯一锁(互斥锁);
做{
//这将等待我们收到信号
条件。等待(锁定);
//可能检查display.rendering()并退出(取决于您的请求)
//处理所有可用的数据
显示.绘图(信号);
}while(display.rendering());//当用户退出时返回false
渲染=假;
};
自动捕获=[&渲染,&数据采集](无效)
{
对于(int i=daq.read_frequency();i-->0;)
daq.record();//在显示信号之前填充缓冲区
渲染=真;
条件。通知_one();
//特别注意:您可以在此处使用
//未获取互斥锁。
do{daq.record();condition.notify_one();}
而(渲染),;
daq.stop();
//按照渲染线程可能有的方式再次发出信号
//一直在打“wait()”电话
条件。通知_one();
};
std::线程渲染\u线程(渲染);
std::线程捕获\u线程(捕获);
呈现_thread.join();
捕获_thread.join();
这样做还将消耗更少的CPU资源,因为当没有数据要处理时,渲染线程将进入睡眠状态。只有实时操作系统才能控制程序流。如果不是在那种操作系统上,试图控制和预测线程调度将失败。真的有必要让每个线程阻塞另一个线程吗?一个是生产者,一个是消费者。应该非常直截了当地使其无锁(这基本上就是您使用atomic的目的)。在这种情况下,atomic不是锁,而是线程之间的“开始/停止”信号。生产者仅在消费者产生足够的数据以显示有意义的内容之前阻止消费者。在此之后,线程独立工作,直到使用者报告用户想要退出。您希望
i-->0
做什么?您意识到它测试i是否为>0
,然后在(不渲染){std::this_thread::yield();}时递减i的值有点残忍,看起来你可能更想要一个旋转锁。另外,在C++11版本中,您显式地增加了对原子的引用的开销。我觉得我应该提到渲染线程实际上读取缓冲区,而忽略了捕获线程的活动。(显示器有点撕裂,但例行程序仅用于粗略的视觉调试)。因此,从它开始的时候,它总是有数据要处理,因为它会绘制整个缓冲区,直到它得到“停止”信号。。。我不确定这是否会改变什么(或者是问题的一部分)。您需要在线程之间进行某种同步。使用boolean是最糟糕的方法,因为它不能帮助调度程序了解代码的意图。使用用于同步的原语,它将帮助调度程序和代码的读取器。当你的代码中有一个“条件”比一个bool更明显的是,前者依赖于其他线程。我认为你是在骑自行车。
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 ();