执行速度太快后,工作线程永久休眠
Worker Thread permanently hibernates, after executing too fast
我正在尝试将线程合并到我的项目中,但遇到一个问题,即仅使用 1 个工作线程就可以使它永久 "fall asleep"。也许我有竞争条件,只是没注意到。
我的 PeriodicThreads
对象维护着一个线程集合。一旦 PeriodicThreads::exec_threads()
被调用,线程就会被通知、被唤醒并执行它们的任务。之后,他们又睡着了。
这样一个工作线程的功能:
void PeriodicThreads::threadWork(size_t threadId){
//not really used, but need to decalre to use conditional_variable:
std::mutex mutex;
std::unique_lock<std::mutex> lck(mutex);
while (true){
// wait until told to start working on a task:
while (_thread_shouldWork[threadId] == false){
_threads_startSignal.wait(lck);
}
thread_iteration(threadId); //virtual function
_thread_shouldWork[threadId] = false; //vector of flags
_thread_doneSignal.notify_all();
}//end while(true) - run until terminated externally or this whole obj is deleted
}
如您所见,每个线程都在监视标志向量中自己的条目,一旦它发现它的标志为真 - 执行任务然后重置其标志。
这里是可以唤醒所有线程的函数:
std::atomic_bool _threadsWorking =false;
//blocks the current thread until all worker threads have completed:
void PeriodicThreads::exec_threads(){
if(_threadsWorking ){
throw std::runtime_error("you requested exec_threads(), but threads haven't yet finished executing the previous task!");
}
_threadsWorking = true;//NOTICE: doing this after the exception check.
//tell all threads to unpause by setting their flags to 'true'
std::fill(_thread_shouldWork.begin(), _thread_shouldWork.end(), true);
_threads_startSignal.notify_all();
//wait for threads to complete:
std::mutex mutex;
std::unique_lock<std::mutex> lck(mutex); //lock & mutex are not really used.
auto isContinueWaiting = [&]()->bool{
bool threadsWorking = false;
for (size_t i=0; i<_thread_shouldWork.size(); ++i){
threadsWorking |= _thread_shouldWork[i];
}
return threadsWorking;
};
while (isContinueWaiting()){
_thread_doneSignal.wait(lck);
}
_threadsWorking = false;//set atomic to false
}
调用 exec_threads()
可以正常进行数百次或在极少数情况下数千次连续迭代。调用发生在主线程的 while
循环中。它的工作线程处理任务,重置其标志并返回睡眠状态,直到下一个 exec_threads()
,依此类推。
然而,一段时间后,程序突然进入 "hibernation",似乎暂停了,但没有崩溃。
在这样的 "hibernation" 中,在我的 condition_variables 的任何 while-loop
处设置断点实际上从未触发该断点。
我偷偷摸摸地创建了自己的验证线程(与 main
并行)并监视我的 PeriodicThreads
对象。当它进入休眠状态时,我的验证线程不断向控制台输出当前没有线程 运行(PeriodicThreads
的 _threadsWorking
原子永久设置为 false)。但是,在其他测试期间,一旦 "hibernation issue" 开始,原子将保持为 true
。
奇怪的是,如果我强制 PeriodicThreads::run_thread
在重置其标志之前至少休眠 10 微秒,一切都会正常进行,并且不会发生 "hibernation"。否则,如果我们允许线程非常快速地完成它的任务,它可能会导致整个问题。
我将每个 condition_variable
包装在一个 while 循环中,以防止虚假唤醒触发转换,以及 notify_all
在 .wait()
被调用之前被调用的情况。 Link
注意,即使我只有 1 个工作线程也会发生这种情况
可能是什么原因?
编辑
放弃这些矢量标志并仅在具有 1 个工作线程的单个 atomic_bool
上进行测试仍然显示相同的问题。
所有共享数据都应受互斥体保护。互斥量应该(至少)与共享数据具有相同的范围。
您的 _thread_shouldWork
容器是共享数据。您可以创建一个全局互斥锁数组,每个互斥锁都可以保护自己的 _thread_shouldWork
元素。 (见下面的注释)。您还应该至少拥有与互斥量一样多的条件变量。 (您可以对多个不同的条件变量使用 1 个互斥锁,但不应对 1 个条件变量使用多个不同的互斥锁。)
A condition_variable
应该保护 actual 条件(在这种情况下,_thread_shouldWork
的单个元素在任何给定点的状态)和mutex 用于保护包含该条件的变量。
如果您只是使用随机的本地互斥体(就像您在线程代码中那样)或者根本不使用互斥体(在主代码中),那么所有的选择都将失败。这是未定义的行为。尽管我大部分时间都能看到它(幸运地)工作。我怀疑正在发生的事情是工作线程缺少来自主线程的信号。也可能是您的主线程缺少来自工作线程的信号。 (线程A读取状态进入while
循环,然后线程B改变状态并发送通知,然后线程A进入休眠...等待已经发送的通知)
具有本地作用域的互斥体是一个危险信号!
注意:如果您使用的是矢量,则必须小心,因为添加或删除项目会触发调整大小,这将触及元素而无需先获取互斥量(当然矢量不知道您的互斥)。
使用数组时也要注意伪共享
编辑:这是 @Kari 发现的一段视频,有助于解释虚假分享
https://www.youtube.com/watch?v=dznxqe1Uk3E
我正在尝试将线程合并到我的项目中,但遇到一个问题,即仅使用 1 个工作线程就可以使它永久 "fall asleep"。也许我有竞争条件,只是没注意到。
我的 PeriodicThreads
对象维护着一个线程集合。一旦 PeriodicThreads::exec_threads()
被调用,线程就会被通知、被唤醒并执行它们的任务。之后,他们又睡着了。
这样一个工作线程的功能:
void PeriodicThreads::threadWork(size_t threadId){
//not really used, but need to decalre to use conditional_variable:
std::mutex mutex;
std::unique_lock<std::mutex> lck(mutex);
while (true){
// wait until told to start working on a task:
while (_thread_shouldWork[threadId] == false){
_threads_startSignal.wait(lck);
}
thread_iteration(threadId); //virtual function
_thread_shouldWork[threadId] = false; //vector of flags
_thread_doneSignal.notify_all();
}//end while(true) - run until terminated externally or this whole obj is deleted
}
如您所见,每个线程都在监视标志向量中自己的条目,一旦它发现它的标志为真 - 执行任务然后重置其标志。
这里是可以唤醒所有线程的函数:
std::atomic_bool _threadsWorking =false;
//blocks the current thread until all worker threads have completed:
void PeriodicThreads::exec_threads(){
if(_threadsWorking ){
throw std::runtime_error("you requested exec_threads(), but threads haven't yet finished executing the previous task!");
}
_threadsWorking = true;//NOTICE: doing this after the exception check.
//tell all threads to unpause by setting their flags to 'true'
std::fill(_thread_shouldWork.begin(), _thread_shouldWork.end(), true);
_threads_startSignal.notify_all();
//wait for threads to complete:
std::mutex mutex;
std::unique_lock<std::mutex> lck(mutex); //lock & mutex are not really used.
auto isContinueWaiting = [&]()->bool{
bool threadsWorking = false;
for (size_t i=0; i<_thread_shouldWork.size(); ++i){
threadsWorking |= _thread_shouldWork[i];
}
return threadsWorking;
};
while (isContinueWaiting()){
_thread_doneSignal.wait(lck);
}
_threadsWorking = false;//set atomic to false
}
调用 exec_threads()
可以正常进行数百次或在极少数情况下数千次连续迭代。调用发生在主线程的 while
循环中。它的工作线程处理任务,重置其标志并返回睡眠状态,直到下一个 exec_threads()
,依此类推。
然而,一段时间后,程序突然进入 "hibernation",似乎暂停了,但没有崩溃。
在这样的 "hibernation" 中,在我的 condition_variables 的任何 while-loop
处设置断点实际上从未触发该断点。
我偷偷摸摸地创建了自己的验证线程(与 main
并行)并监视我的 PeriodicThreads
对象。当它进入休眠状态时,我的验证线程不断向控制台输出当前没有线程 运行(PeriodicThreads
的 _threadsWorking
原子永久设置为 false)。但是,在其他测试期间,一旦 "hibernation issue" 开始,原子将保持为 true
。
奇怪的是,如果我强制 PeriodicThreads::run_thread
在重置其标志之前至少休眠 10 微秒,一切都会正常进行,并且不会发生 "hibernation"。否则,如果我们允许线程非常快速地完成它的任务,它可能会导致整个问题。
我将每个 condition_variable
包装在一个 while 循环中,以防止虚假唤醒触发转换,以及 notify_all
在 .wait()
被调用之前被调用的情况。 Link
注意,即使我只有 1 个工作线程也会发生这种情况
可能是什么原因?
编辑
放弃这些矢量标志并仅在具有 1 个工作线程的单个 atomic_bool
上进行测试仍然显示相同的问题。
所有共享数据都应受互斥体保护。互斥量应该(至少)与共享数据具有相同的范围。
您的 _thread_shouldWork
容器是共享数据。您可以创建一个全局互斥锁数组,每个互斥锁都可以保护自己的 _thread_shouldWork
元素。 (见下面的注释)。您还应该至少拥有与互斥量一样多的条件变量。 (您可以对多个不同的条件变量使用 1 个互斥锁,但不应对 1 个条件变量使用多个不同的互斥锁。)
A condition_variable
应该保护 actual 条件(在这种情况下,_thread_shouldWork
的单个元素在任何给定点的状态)和mutex 用于保护包含该条件的变量。
如果您只是使用随机的本地互斥体(就像您在线程代码中那样)或者根本不使用互斥体(在主代码中),那么所有的选择都将失败。这是未定义的行为。尽管我大部分时间都能看到它(幸运地)工作。我怀疑正在发生的事情是工作线程缺少来自主线程的信号。也可能是您的主线程缺少来自工作线程的信号。 (线程A读取状态进入while
循环,然后线程B改变状态并发送通知,然后线程A进入休眠...等待已经发送的通知)
具有本地作用域的互斥体是一个危险信号!
注意:如果您使用的是矢量,则必须小心,因为添加或删除项目会触发调整大小,这将触及元素而无需先获取互斥量(当然矢量不知道您的互斥)。
使用数组时也要注意伪共享
编辑:这是 @Kari 发现的一段视频,有助于解释虚假分享 https://www.youtube.com/watch?v=dznxqe1Uk3E