原子指令和变量更新可见性
-
06-07-2019 - |
题
在大多数常见平台上(最重要的是x86;我明白有些平台有极其困难的内存模型,几乎没有保证对多线程有用,但我不关心罕见的反例),是以下代码安全
主题1:
someVariable = doStuff();
atomicSet(stuffDoneFlag, 1);
主题2:
while(!atomicRead(stuffDoneFlag)) {} // Wait for stuffDoneFlag to be set.
doMoreStuff(someVariable);
假设原子操作的标准合理实现:
- 在
atomicSet()
被调用之前,是否保证线程1对someVariable
的赋值完成? - 线程2是否保证在调用
doMoreStuff()
之前看到someVariable
的赋值,前提是它原子地读取stuffDoneFlag
?
醇>
- 我正在使用的原子操作的实现在每个中都包含x86
LOCK
指令 操作,如果有帮助。 - 假设
stuffDoneFlag
以某种方式正确清除。怎么不重要。 - 这是一个非常简化的例子。我以这种方式创建它,这样你就不必理解问题的整个上下文来回答它。我知道它效率不高。 醇>
编辑:
解决方案
如果您的实际x86代码在Thread 1中的atomicSet中存储到someVariable,并且在Thread 2中的atomicRead中加载之后加载someVariable,那么您应该没问题。 英特尔软件开发人员手册第3A卷在8.2节中指定了x86的内存模型,并且这里的线程内存储和负载加载限制就足够了。
但是,可能没有任何东西阻止您的编译器重新排序从您在原子操作中使用的任何更高级语言生成的指令。
其他提示
1)是
2)是
两者都有效。
此代码看起来是线程安全的,但我质疑 spinlock 的效率(同时循环)除非你只是在很短的时间内旋转。在任何给定的系统上都无法保证线程2不会完全占用所有处理时间。
我建议使用一些实际的同步原语(看起来像 boost :: condition_variable 就是你想要的)而不是依赖于自旋锁。
原子指令确保线程2在线程2继续之前等待线程1完成设置变量。但是,有两个关键问题:
1)必须声明 someVariable
' volatile '以确保编译器不优化它的分配,例如将其存储在寄存器中或推迟写入。
2)第二个线程在等待信号时阻塞(称为 spinlocking )。您的平台可能提供了更好的锁定和信令主机和机制,但相对简单的改进是在线程2的 while()
主体中简单地 sleep()
。 p>
dsimcha写道:"假设stuffDoneFlag以某种方式被正确清除。怎么不重要。“ 这不是真的!
让我们看看情景:
- Thread2检查stuffDoneFlag是否为1开始读取someVariable。
- 在Thread2完成读取任务调度程序之前,中断其任务并暂停该任务一段时间。
- Thread1再次访问someVariable并更改内存内容。
- 任务调度程序再次打开Thread2并继续执行该作业,但someVariable的内存内容已更改! 醇>