假设有两个线程分别运行Thread1()
和Thread2()
。线程1只设置一个全局标志来告诉线程2退出,线程2定期检查它是否应该退出。
volatile bool is_terminate = false;
void Thread1()
{
is_terminate = true;
}
void Thread2()
{
while (!is_terminate) {
// ...
}
}
我想问一下上述代码是否安全,假设对is_terminate
的访问是原子的。我已经知道许多材料表明volatile
通常无法确保线程安全。但是在只共享一个原子变量的情况下,我们真的需要使用锁来保护共享变量吗?
答案 0 :(得分:17)
可能是类型的线程安全。
线程安全往往取决于上下文。如果您从未读取,则更新bool 始终线程安全。 如果您确实从中读取,那么答案取决于何时从中读取,以及读取的含义。
在某些CPU上,但不是全部,对bool
类型的对象的写入将是原子的。 x86 CPU通常会使其成为原子,但其他人可能不会。如果更新不是原子的,那么添加volatile
对您没有帮助。
但下一个问题是重新排序。编译器(和CPU)将按指定的顺序对volatile
变量执行读/写操作,而不进行任何重新排序。这很好。
但它无法保证相对于所有非volatile
内存访问重新排序一个volatile
内存访问权限。所以一个常见的例子就是你定义了某种标志以保护对资源的访问,你创建了标志volatile
,然后编译器移动资源访问,以便在之前发生检查国旗。允许这样做,因为它没有重新排序两个volatile
访问的内部排序,而只是volatile
和非volatile
访问。
老实说,我问的问题是为什么不正确地做到?
volatile
可能会在这种情况下起作用,但为什么不为自己省去麻烦,更清楚地说明它是正确的?在它周围拍打一个记忆障碍。
答案 1 :(得分:11)
它不是线程安全的。
例如,如果线程在具有单独高速缓存的CPU上运行,则没有语言规则表明在编写volatile变量时要同步高速缓存。如果有的话,另一个线程可能在很长一段时间内都看不到变化。
以另一种方式回答:
如果volatile
足以保证线程安全,为什么C ++ 0x会用原子操作添加整章?
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2047.html
答案 2 :(得分:4)
首先, volatile 用于在c / c ++中禁用编译优化。请参阅this了解volatile。
原子的核心是单词对齐和 is_terminate 的大小,如果 is_terminate 的大小小于机器本机大小并且对齐,那么它的R和W是原子的
在你的上下文中,有或没有volatile,thread2可能在thread1修改后读取旧值,但是thread2最终可以读取它。
如果最终读取对您没问题,那么您的代码就是线程安全。
答案 3 :(得分:2)
这是安全的,因为一个线程只是在阅读而一个只是在写作。
线程并没有真正共享那个标志,一个正在阅读,一个正在写。你不能参加比赛,因为另一个线程永远不会写出错误的结果,而阅读线程永远不会读取错误的结果。简单。
答案 4 :(得分:1)
不,不是。它可以是线程安全的 如果值访问是原子的 ,但在C ++中你不能假设变量访问是线程安全的,除非你使用一些特定于编译器的结构或同步原语。
答案 5 :(得分:1)
仍然不安全。您应该使用synchronizaton来访问is_terminate
不能保证对bool的访问是原子操作。
答案 6 :(得分:1)
我相信这段代码是安全的,直到两个线程都没有写bool
(你已经提到过值访问是原子)。
答案 7 :(得分:1)
假设volatile
关键字强加任何类型的线程安全性的一个大问题是C或C ++标准在它们描述的抽象机器中没有线程概念。
标准对volatile
关键字施加的保证仅在线程内有效 - 而不是在多个线程之间。
这使得实现者可以完全自由地在线程上做任何他们喜欢的事情。如果他们选择将volatile
关键字实现为跨线程安全,那么您很幸运。但通常情况并非如此。
答案 8 :(得分:-1)
此代码似乎不是线程安全的。原因很容易解释。
问题出在下面的代码行
“is_terminate = true;”
即使对“is_terminate”的访问是原子的,上面的语句也不是原子的。 该声明包含多个操作。像加载“is_terminate”并更新“is_terminate”。
现在问题是如果is_terminate已加载但未更新,并且线程切换到另一个。 现在线程2的预期结果为真,但它不会得到它。
确保“is_terminate = true;”是原子的。所以锁定它。
希望它有所帮助。