我需要设置另一个线程退出的标志。另一个线程不时地检查退出标志。我是否必须使用atomic作为标志,或者仅仅使用普通bool就足够了,为什么(用一个示例说明如果我使用普通bool可能会出现什么问题)?
#include <future>
bool exit = false;
void thread_fn()
{
while(!exit)
{
//do stuff
if(exit) break;
//do stuff
}
}
int main()
{
auto f = std::async(std::launch::async, thread_fn);
//do stuff
exit = true;
f.get();
}
我必须使用原子"退出"bool变量吗?
对。
要么使用atomic<bool>
,要么通过(例如)std::mutex
使用手动同步。您的程序当前包含数据争用,一个线程可能正在读取一个变量,而另一个线程正在写入它。这是未定义行为。
c++ 11标准第1.10/21段:
如果一个程序在不同的线程中包含两个冲突的操作,则该程序的执行包含数据争用。至少其中一种不是原子的,两者都不先于另一种发生。任何这样的数据竞争都会导致未定义的行为。
冲突的定义见第1.10/4段:
两个表达式求值如果其中一个修改了内存位置(1.7)而另一个修改了,则冲突访问或修改相同的内存位置。
是的,你的必须有一些同步。如你所说,最简单的方法是使用atomic<bool>
。
形式上,正如@AndyProwl所说,语言定义说这里不使用原子会产生未定义的行为。这是有充分理由的。
首先,对变量的读写可以在中途被线程切换中断;另一个线程可能会看到一个部分写入的值,或者如果它修改了这个值,原始线程将看到一个混合的值。其次,当两个线程在不同的内核上运行时,它们有单独的缓存;写入值将其存储在缓存中,但不会更新其他缓存,因此线程可能看不到另一个线程写入的值。第三,编译器可以根据它所看到的重新组织代码;在示例代码中,如果循环内没有改变exit
的值,编译器没有任何理由怀疑该值会改变;它可以把循环变成while(1)
。
原子解决了这三个问题
实际上,在这个特定的示例中,使用纯bool没有任何问题。唯一需要注意的是将bool退出变量声明为volatile以将其保存在内存中。CISC和RISC体系结构都将bool读/写严格地实现为原子处理器指令。此外,现代多核处理器具有先进的智能缓存实现。所以,任何内存屏障都是不必要的。标准引用不适合这种特殊情况,因为它只处理一个写入和一个线程的读取。