考虑下面的 spin_lock()
实现,最初来自 this answer :
void spin_lock(volatile bool* lock) {
for (;;) {
// inserts an acquire memory barrier and a compiler barrier
if (!__atomic_test_and_set(lock, __ATOMIC_ACQUIRE))
return;
while (*lock) // no barriers; is it OK?
cpu_relax();
}
}
我已经知道的:
volatile
防止编译器在while
循环的每次迭代中优化出*lock
重新读取;volatile
inserts neither memory nor compiler barriers ;- 这样的实现实际上在 GCC 中适用于
x86
(例如在 Linux 内核中)和其他一些架构; - 至少有一个内存和编译器屏障is required在通用架构的
spin_lock()
实现中;此示例将它们插入__atomic_test_and_set()
.
问题:
这里的
volatile
是否足够,或者是否有任何架构或编译器在while
循环中需要内存或编译器屏障或原子操作?1.1 根据
C++
标准?1.2 在实践中,对于已知的架构和编译器,特别是 GCC 及其支持的平台?
- 这个实现在 GCC 和 Linux 支持的所有架构上是否安全? (在某些架构上至少效率低下,对吧?)
- 根据
C++11
及其内存模型,while
循环是否安全?
有几个相关的问题,但我无法从它们中构建出明确而明确的答案:
Q: Memory barrier in a single thread
In principle: Yes, if program execution moves from one core to the next, it might not see all writes that occurred on the previous core.
Q: memory barrier and cache flush
On pretty much all modern architectures, caches (like the L1 and L2 caches) are ensured coherent by hardware. There is no need to flush any cache to make memory visible to other CPUs.
Q: Do spin locks always require a memory barrier? Is spinning on a memory barrier expensive?
Q: Do you expect that future CPU generations are not cache coherent?
最佳答案
这很重要:在 C++ 中,volatile
与并发性完全没有关系! volatile
的目的是告诉编译器它不应优化对受影响对象的访问。它确实不告诉 CPU 任何事情,主要是因为 CPU 已经知道内存是否是 volatile
。 volatile
的目的是有效地处理内存映射 I/O。
C++ 标准在第 1.10 节 [intro.multithread] 中非常清楚,对在一个线程中修改并在另一个线程中访问(修改或读取)的对象的非同步访问是未定义的行为。避免未定义行为的同步原语是库组件,如原子类或互斥锁。该子句仅在信号的上下文中(即,作为 volatile sigatomic_t
)和向前进展的上下文中(即,线程最终将执行具有可观察到的效果,例如访问 volatile
对象或执行 I/O)。没有提到与同步结合使用的 volatile
。
因此,对跨线程共享的变量的不同步评估会导致未定义的行为。是否声明为 volatile
与这种未定义的行为无关。
关于c++ - 忙等待循环中是否需要内存屏障或原子操作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32677667/