std::thread 在无限循环中随机时间后锁定
Posted
技术标签:
【中文标题】std::thread 在无限循环中随机时间后锁定【英文标题】:std::thread locks after a random time in an endless loop 【发布时间】:2015-08-23 08:52:21 【问题描述】:我正在尝试为我的主应用程序实现 3 个额外的线程来执行非共享操作。
起初我认为它可以工作,因为如果我取消注释 WorkerThread 函数中的最后一个 printf 调用,它不会在随机时间段后锁定在 WaitThread() 处。如果没有 printf,它有时需要几秒钟才能锁定在 mWaitCond.Wait() 函数上,有时是在启动之后。 printf 似乎修复了线程的时间。
应用程序不会崩溃,只是应用程序的 cpu 使用率变为 0%(以及每个线程)并且它没有响应。在 Visual Studio 调试器中停止将 WaitThread() 函数中的 while(mWakeUp) mWaitCondition.Wait() 行显示为当前位置。它还表明 mWakeUp 对于所有线程都是错误的,所以它不应该停留在那个 while 循环中。
我的设计理念:
-
SetupThreads() 在进入主无限循环之前被调用
在无限循环中,WorkerInit() 被调用来唤醒线程
在访问 3 个线程的数据之前,会调用 WorkerWait() 等待它们完成
在 WorkerThread 函数内部(由每个线程调用),我锁定互斥体并等待线程被唤醒或停止
处理完数据后,wakeUp 设置为 false 并且 condition_variable 通知
可能是waitthread一个接一个地等待线程,当它等待让我们说索引0处的线程时,索引2处的线程继续运行?
static const ui32 NumContexts = 3;
// array of pointers to threads
std::thread* mThreadHandles[NumContexts];
// wakup
std::atomic<bool> mWakeUp[NumContexts];
std::mutex mWakeMutex[NumContexts];
std::condition_variable mWakeCondition[NumContexts];
// wait for thread to finish task
std::mutex mWaitMutex[NumContexts];
std::condition_variable mWaitCondition[NumContexts];
// stop signal
std::atomic<bool> mStop[NumContexts];
void Framework::SetupThreading()
// create and start threads
for (int i = 0; i < NumContexts; i++)
this->mWakeUp[i] = false;
this->mStop[i] = false;
this->mThreadHandles[i] = new std::thread(&Framework::WorkerThread, this, reinterpret_cast<void*>(i));
//---------------------------------------------
void Framework::WakeUpThread(int i)
//auto lock = std::unique_lock<std::mutex>(this->mWakeMutex[i]);
std::lock_guard<std::mutex> lock(this->mWakeMutex[i]);
//printf("Waking up thread %i \n", i);
this->mWakeUp[i] = true;
this->mWakeCondition[i].notify_one();
// THIS FUNCTION LOCKS
//---------------------------------------------
void Framework::WaitThread(int i)
auto lock = std::unique_lock<std::mutex>(this->mWaitMutex[i]);
//printf("Waiting for thread %i to finish \n", i);
while (this->mWakeUp[i])
this->mWaitCondition[i].wait(lock);
//printf("Thread %i finished! \n", i);
//---------------------------------------------
void Framework::StopThread(int i)
auto lock = std::unique_lock<std::mutex>(this->mWakeMutex[i]);
printf("Sending stop signal for thread %i \n", i);
this->mStop[i] = true;
this->mWakeCondition[i].notify_one();
//---------------------------------------------
void Framework::JoinThread(int i)
printf("Waiting for join of thread %i \n", i);
this->mThreadHandles[i]->join();
printf("Thread %i joined! \n", i);
// THESE ARE CALLED IN THE MAIN LOOP
//---------------------------------------------
void Framework::WorkerInit()
for (int i = 0; i < NumContexts; i++)
this->WakeUpThread(i);
void Framework::WorkerWait()
for (int i = 0; i < NumContexts; i++)
this->WaitThread(i);
// THE FUNCTION CALLED BY THE THREADS
//---------------------------------------------
void Framework::WorkerThread(LPVOID workerIndex)
int threadIndex = reinterpret_cast<int>(workerIndex);
while (threadIndex < NumContexts && threadIndex >= 0)
auto lock = std::unique_lock<std::mutex>(this->mWakeMutex[threadIndex]);
//printf("thread %i: waiting for wakeup or stop signal...\n", threadIndex);
// not stopped nor woken up? continue to wait
while (this->mWakeUp[threadIndex] == false && this->mStop[threadIndex] == false)
this->mWakeCondition[threadIndex].wait(lock);
// stop signal sent?
if (this->mStop[threadIndex])
//printf("thread %i: got stop signal!\n", threadIndex);
return;
//printf("thread %i: got wakeup signal!\n", threadIndex);
// lock unlocks here (lock destructor)
// printf("thread %i: running the task...\n", threadIndex);
// RUN CODE HERE
//printf("thread %i finished! Sending signal!...\n", threadIndex);
// m_wakeup is atomic so there is no concurrency issue with wait()
this->mWakeUp[threadIndex] = false;
this->mWaitCondition[threadIndex].notify_all();
【问题讨论】:
这可能不是您潜在死锁问题的解决方案,但您的代码似乎有几个经典的数据竞争。您可以使用互斥锁来避免这些。要弄清楚它们在哪里:询问每个变量是否可以从多个线程访问,其中至少有一个线程修改变量。如果没有同步原语,比如互斥锁,那就是数据竞争。还要考虑一下,对于您的问题,可能有一种更简单的方法。 我使用 D3D12s CommandLists 来记录命令。每个线程都有自己的数据数组和命令列表,因此不能共享访问任何数据。如果我使用 printf 显示线程的状态,它也可以正常工作:codepaste.net/wmqgao 如果我不使用线程处理任何内容,它也会锁定 例如this->mWakeUp[i] = false;
你修改了没有同步原语的状态。请注意,原子性本身并不是您需要的那种并发性。但是,在强排序的 CPU 上,这个问题不会变得明显。这仍然是一场数据竞赛。
@CouchDeveloper 不,对原子变量的访问不能 - 根据定义 - 参与 data 竞赛。是的,商店this->mWakeUp[threadIndex] = false;
和WorkerThread
中的通知this->mWaitCondition[threadIndex].notify_all();
之间存在竞争,但这不是数据竞争。
@Casey 你是对的,std::atomic
保证访问没有竞争条件。但这仅对这个变量有保证,没有上下文(好吧,我们不知道底层的内存屏障)。如果您需要涉及多个变量的“同步于”和“之前发生”关系,则应使用互斥锁等同步原语。
【参考方案1】:
如果线程的 CPU 使用率为零,那么它不会在 while 循环中旋转,而是在 wait() 上阻塞。在 wait() 解除阻塞之前,不会测试循环条件。
检查调试器中的调用堆栈进行验证,暂停位置可能只是指示您的源代码中的返回位置,而不是当前位置。
还要检查 WorkerThread 实例的状态——它们是否正在运行并调用notify_all()
?你的调试器线程知道吗?
我不确定我是否理解您的设计或意图,但从表面上看,这对我来说似乎有些过于复杂,并且已经成熟到陷入僵局了。
【讨论】:
在 notify_all() 函数调用之后打印告诉我所有 3 个索引在挂断之前都达到了该点。之后,调试器的位置在 WorkerThread 函数内的 wait() 调用处。带有线程位置的屏幕截图。卡在等待:i.imgur.com/dYcXZAL.png 关于设计:我尝试运行 3 个线程,每帧都工作,我需要等待所有 3 个线程都完成,然后才能继续在主线程上工作。 @anthom 使用 futures 和 promises 会让这个问题很容易解决。不幸的是,std::future 在当前的标准中有几个缺点。也许使用 boost 未来的实现是一个更好的选择。 theboostcpplibraries.com/boost.thread-futures-and-promises, boost.org/doc/libs/1_58_0/doc/html/thread/… @anthom :屏幕截图应直接添加到问题中,而不是评论中的站外链接。您可以更有效地使用调试器;当您可以使用断点检查到达某个位置时,为什么还要插入打印?您的屏幕截图仅显示“自动”选项卡;在这种情况下,调用堆栈和线程选项卡可能更相关。以上是关于std::thread 在无限循环中随机时间后锁定的主要内容,如果未能解决你的问题,请参考以下文章
std::thread::join 无限期地阻塞在 main 之外