空的 while 循环不检查条件

5
在一个多线程的C++程序中,我有类似于以下代码在一个线程中运行:
while(obj->member) { } // waiting for obj->member to be set to false in another thread

在另一个线程中,obj->member被设置为false。但即使它被设置为false,循环也不会终止。如果我将其更改为以下内容:

while(obj->member) { Sleep(1) }

当obj->member在另一个线程中被设置为false时,它的运行结果符合预期并崩溃了。
为什么会这样呢?


5
请不要使用繁忙等待(busy wait)- 学习条件变量或类似的同步原语。 - user3458
什么?我不知道那是什么意思。 - Hock
我赞同@Arkadiy所说的话(如果你不知道这意味着什么,那么在编写任何多线程代码之前,你需要大量阅读)。除了繁忙等待(这只是一种不好的形式),你的代码也不是线程安全的。然而,在这个确切的情况下,考虑到你正在做的事情的简单性,我认为你不会在任何常见的CPU架构上遇到问题。 - rmeador
5个回答

10

尝试将成员变量volatile化。这将强制每次使用它时都从内存中获取,而不是从CPU寄存器(这是编译器可能进行优化的方式)。


6

Obj必须声明为volatile。这告诉编译器它的值可能会被其他线程改变。

当你添加Sleep时,编译器知道其他线程正在工作,并假设它可能会改变。


编译器知道volatile关键字的作用,而不是Sleep的存在。 - Carey Gregory
除了显然编译器可以根据 OP 的代码中的 Sleep(),知道这一点。 - James Curran
也许吧,但我持怀疑态度。Sleep的存在并不意味着进程中有其他线程。即使在单线程应用程序中,将Sleep或其他yielding操作添加到紧密循环中也是非常常见的。我认为更有可能的是OP的无休止循环使另一个线程饥饿到无法设置标志。 - Carey Gregory

4
事实上,它能够工作大多是偶然的。如果您想要可靠地执行此类操作,几乎需要使用某种操作系统提供的IPC机制,可能需要使用Boost Interprocess(例如,互斥或信号量)来提供更加便携的前端。
尽管经常被提出作为解决此类问题的方案,但volatile既不是必需的也不足够。它可能会损害性能(很多),但仍然不足以使线程正确工作。如果您正在为.NET编程,Microsoft已定义了其版本的volatile以提供(至少一定程度的)线程安全性。否则(在真正的C或C++中),它的实际用途很小,并且可能会造成相当大的伤害。
我还应该提到,这个错误并非第一次出现。早在很久以前,就有权威人士Andre Alexandrescu在Doctor Dobbs上写了一篇相当重要的关于使用volatile进行线程编程的文章。他后来意识到自己是错的,但(在很大程度上)已经造成了损害--尤其是因为volatile非常接近正确的做法,所以很容易将其误认为在线程编程中是正确/有用的。

2
更好的方法是使用事件而不是布尔值。使用事件,您可以在不使用任何CPU的情况下等待它,而且不需要睡眠或使用易变量。

0
也许你的线程库在创建线程时会复制对象,除非将成员声明为静态,否则该成员并不真正在线程之间共享。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接