在繁忙等待循环中是否需要内存屏障或原子操作?
在繁忙等待循环中是否需要内存屏障或原子操作?
考虑以下的 spin_lock()
实现,最初来自于这个答案:
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
不会插入内存栅栏或编译器栅栏;- 这样的实现在
x86
(例如在 Linux 内核中)和其他一些架构上实际上是有效的; spin_lock()
实现在通用架构中至少需要一个内存和一个编译器栅栏是必需的,这个示例在__atomic_test_and_set()
中插入了它们。
问题:
- 在这里是否只使用
volatile
就足够了,或者是否有任何需要在while
循环中需要内存或编译器栅栏或原子操作的架构或编译器?1.1 根据
C++
标准?1.2 在实践中,对于已知的架构和编译器,特别是对于 GCC 和它支持的平台?
- 这个实现在 GCC 和 Linux 支持的所有架构上是否安全?(至少在一些架构上是低效的,对吧?)
- 根据
C++11
和其内存模型,这个while
循环是否安全?
有几个相关的问题,但我无法从中得出一个明确和明确的答案:
- Q: 单线程中的内存屏障
原则上:是的,如果程序执行从一个核心移到下一个核心,它可能无法看到在前一个核心上发生的所有写入。
- Q: 内存屏障和缓存刷新
在几乎所有现代架构上,缓存(如 L1 和 L2 缓存)由硬件保证一致性。没有必要刷新任何缓存以使内存对其他 CPU 可见。
- Q: 我的自旋锁实现是否正确和最优?
- Q: 自旋锁是否总是需要内存屏障?自旋一个内存屏障是否昂贵?
- Q: 你是否预计未来的 CPU 代际不是缓存一致的?
在一个忙等待循环中,是否需要内存屏障或原子操作?
这个问题的出现原因是因为C++中的volatile关键字与并发性无关。volatile的目的是告诉编译器不要优化对受影响对象的访问。它并不能告诉CPU任何信息,主要是因为CPU已经知道内存是否为volatile。volatile的目的实际上是处理内存映射的I/O。
C++标准在1.10节[intro.multithread]中非常明确地指出,在一个线程中修改了一个对象并在另一个线程中访问(修改或读取)该对象的未同步访问是未定义的行为。避免未定义行为的同步原语是库组件,比如原子类或互斥锁。这个条款只在信号的上下文中提到了volatile(即作为volatile sigatomic_t),以及在前进的上下文中(即一个线程最终会执行具有可观察效果的操作,比如访问一个volatile对象或进行I/O)。没有提到与同步相关的volatile。
因此,对一个在线程之间共享的变量进行未同步的访问会导致未定义的行为。无论它是否声明为volatile,对于这个未定义的行为来说并没有关系。
所以,对于问题3,答案肯定是否定的,根据C++标准来说是不安全的。
话虽如此,GCC手册指出,对volatile*进行解引用实际上会从内存中重新加载值,并且不会被优化掉,所以在GCC上,问题的提问者对于它的含义是正确的。
不对,这个结论是错误的:编译器会导致数据被加载,但是在多核系统的上下文中,这是否影响CPU的行为完全无关。有些CPU实际上会重新读取数据(例如当前的x86系统),而其他CPU则不会。
我认为我们完全不是持不同意见,Dietmar。提问者意识到volatile并不能保证缓存一致性。
所以问题的出现的原因是,在某些体系结构中,处理器可能不会更新其本地缓存,除非被指示这样做。这可能导致代码进入一个无限循环,其中对*lock的读取总是看到相同的值。
解决方法是使用内存屏障或原子操作来确保读/写对其他处理器可见。在C++中,使用atomic_load_explicit函数和memory_order_relaxed参数来替代非原子读取,以避免未定义的行为。另外,可以使用volatile关键字来表示地址可能引用由硬件控制的位置,这样编译器就会按照预期进行处理。
,解决这个问题的方法是使用内存屏障或原子操作来确保读/写对其他处理器可见,避免未定义的行为,并根据具体体系结构和编译器的特性做出适当调整。
在一个繁忙的等待循环中,是否需要内存屏障或原子操作?这个问题的出现的原因是因为在多线程编程中,我们需要确保线程之间的同步和互斥,以避免数据竞争和不确定的行为。在讨论中,有人提出了使用volatile关键字是否足够,或者在while循环中是否需要内存屏障或原子操作。
根据对该的问题和引用的C++标准,我们可以得出以下结论:
- volatile关键字可以阻止编译器对变量进行优化,使其每次都从内存中读取,适用于与信号处理程序进行通信。但是,它并不能保证在写入volatile变量后多久才能对其他线程可见。
- 内存屏障或原子操作可以确保线程之间的同步和一致性,通过创建一个内存顺序或同步关系,确保消息在线程之间传递。
然而,根据讨论中提到的,volatile关键字并不足以保证在忙等待循环中对变量的更改立即可见。在某些情况下,需要更强的同步机制,如内存屏障或原子操作。具体来说,如果代码没有执行任何与线程间同步相关的操作,那么在某些当前的硬件上,代码可能会失败。
而根据引用的C++标准和一些实践经验,对于GCC和支持的平台来说,代码不符合C++标准中对CPU的一般化定义,因此可能无法在遵循标准的C++版本中正常工作。
总结起来,对于一个繁忙的等待循环,如果需要保证变量的更改能够被其他线程立即看到,应该考虑使用内存屏障或原子操作来确保线程之间的同步和一致性。而仅仅使用volatile关键字是不够的,因为它不能保证在写入变量后多久才能对其他线程可见。
以上是关于在繁忙等待循环中是否需要内存屏障或原子操作的讨论和解决方法。希望对你有所帮助!