我希望我的线程更优雅地关闭,所以我试图实现一个简单的信令机制。我不认为我想要一个完全事件驱动的线程,所以我有一个工人用一个方法来使用一个关键部分Monitor
(相当于一个C#lock
我相信)来优雅地停止它:
DrawingThread.h
class DrawingThread {
bool stopRequested;
Runtime::Monitor CSMonitor;
CPInfo *pPInfo;
//More..
}
DrawingThread.cpp
void DrawingThread::Run() {
if (!stopRequested)
//Time consuming call#1
if (!stopRequested) {
CSMonitor.Enter();
pPInfo = new CPInfo(/**/);
//Not time consuming but pPInfo must either be null or constructed.
CSMonitor.Exit();
}
if (!stopRequested) {
pPInfo->foobar(/**/);//Time consuming and can be signalled
}
if (!stopRequested) {
//One more optional but time consuming call.
}
}
void DrawingThread::RequestStop() {
CSMonitor.Enter();
stopRequested = true;
if (pPInfo) pPInfo->RequestStop();
CSMonitor.Exit();
}
我理解(至少在Windows中)Monitor
/ lock
是最便宜的线程同步原语,但我很想避免过度使用。我应该包装每个读取此布尔标志吗?它被初始化为false,并且只在请求停止时设置为true(如果在任务完成之前请求它)。
我的导师建议甚至保护bool
,因为读/写可能不是原子的。我认为这一次射击旗是证明规则的例外吗?
答案 0 :(得分:45)
在没有同步的情况下读取可能在不同线程中修改的内容永远不可能。需要什么级别的同步取决于您实际阅读的内容。对于原始类型,您应该查看原子类型,例如:以std::atomic<bool>
。
始终需要同步的原因是处理器将具有可能在高速缓存行中共享的数据。如果没有同步,则没有理由将此值更新为可能在其他线程中更改的值。更糟糕的是,如果没有同步,如果存储在该值附近的内容被更改和同步,则可能会写入错误的值。
答案 1 :(得分:10)
布尔赋值是原子的。那不是问题。
问题在于,由于编译器或CPU指令重新排序或数据缓存,线程可能看不到由不同线程完成的变量的更改(即,读取布尔标志的线程可能会读取缓存的值,而不是实际的更新值。)
解决方案是一个内存围栏,它实际上是由锁定语句隐式添加的,但是对于单个变量来说它是过度的。只需将其声明为std::atomic<bool>
。
答案 2 :(得分:6)
我相信答案是“这取决于”。如果您正在使用C ++ 03,则标准中未定义线程,您必须阅读编译器和线程库所说的内容,尽管这种事情通常被称为“良性竞赛”{{ 3}}
如果你正在使用C ++ 11,那么良性竞赛是未定义的行为。即使未定义的行为对底层数据类型没有意义。问题是编译器可以假定程序没有未定义的行为,and is usually OK(另请参见从那里链接的第1部分和第2部分)。例如,您的编译器可能决定一次读取标志并缓存该值,因为它是未定义的行为,在没有某种互斥或内存屏障的情况下写入另一个线程中的变量。
当然,很可能你的编译器承诺不进行优化。你需要看看。
最简单的解决方案是在C ++ 11中使用std::atomic<bool>
,或在其他地方使用and make optimizations based on that。
答案 3 :(得分:1)
不,您必须保护每个访问权限,因为现代编译器和cpus重新排序代码而不考虑您的多线程任务。来自不同线程的读访问可能有效,但不必工作。