我知道volatile通知编译器可能会更改值,但为了完成此功能,编译器是否需要引入内存栅栏才能使其工作?
根据我的理解,易失性对象的操作顺序不能重新排序,必须保留.这似乎暗示一些内存栅栏是必要的,并且没有真正解决方法.我说的是对的吗?
在这个相关问题上有一个有趣的讨论
...对于不同的volatile变量的访问不能由编译器重新排序,只要它们出现在单独的完整表达式中......对于线程安全而言volatile是无用的,但不是由于他给出的原因.这不是因为编译器可能会重新排序对易失性对象的访问,而是因为CPU可能会重新排序它们.原子操作和内存屏障阻止编译器和CPU重新排序
...从C++标准的角度来看,编译器执行某些操作与编译器发出导致硬件执行某些操作的指令之间没有区别.如果CPU可能重新排序对volatiles的访问,则标准不要求保留其订单....
... C++标准没有对重新排序有什么区别.你不能争辩说CPU可以重新排序它们没有可观察到的影响,所以没关系--C++标准将它们的顺序定义为可观察的.如果编译器生成的代码使平台能够满足标准要求,则编译器在平台上符合C++标准.如果标准要求对挥发物的访问不能重新排序,则重新排序它们的平台不符合要求....
我的观点是,如果C++标准禁止编译器重新排序对不同易失性的访问,理论上这种访问的顺序是程序可观察行为的一部分,那么它还要求编译器发出禁止CPU执行的代码所以.该标准没有区分编译器的作用以及编译器生成的代码使CPU执行的操作.
这确实产生了两个问题:它们中的任何一个是"正确的"吗?实际的实现到底做了什么?
我正在使用旋转锁来保护非常小的关键部分.争用情况非常罕见所以自旋锁是比常规的互斥体更合适.
我目前的代码如下,并假设x86和GCC:
volatile int exclusion = 0;
void lock() {
while (__sync_lock_test_and_set(&exclusion, 1)) {
// Do nothing. This GCC builtin instruction
// ensures memory barrier.
}
}
void unlock() {
__sync_synchronize(); // Memory barrier.
exclusion = 0;
}
Run Code Online (Sandbox Code Playgroud)
所以我想知道:
__sync_lock_release.我不是记忆障碍的专家,所以我不确定我是否可以使用它而不是__sync_synchronize.我不在乎在所有有关争.有可能是1,也许2其他线程试图每过一次锁自旋锁天.
假设有运行两个线程Thread1(),并Thread2()分别.线程1只设置一个全局标志来告诉线程2退出,线程2定期检查它是否应该退出.
volatile bool is_terminate = false;
void Thread1()
{
is_terminate = true;
}
void Thread2()
{
while (!is_terminate) {
// ...
}
}
Run Code Online (Sandbox Code Playgroud)
我想问上面的代码是否安全,假设访问is_terminate是原子的.我已经知道许多材料状态,volatile一般不能确保线程安全.但是在只共享一个原子变量的情况下,我们真的需要使用锁来保护共享变量吗?
有一个流行的自旋锁互斥锁版本在互联网上传播,人们可能会在Anthony Williams的书(C++ Concurrency in Action)中遇到这种版本.这里是:
class SpinLock
{
std::atomic_flag locked;
public:
SpinLock() :
locked{ATOMIC_FLAG_INIT}
{
}
void lock()
{
while(locked.test_and_set(std::memory_order_acquire));
}
void unlock()
{
locked.clear(std::memory_order_release);
}
};
Run Code Online (Sandbox Code Playgroud)
我不明白的是为什么每个人都使用std::memory_order_acquire的test_and_set是一个RMW操作.为什么不std::memory_acq_rel呢?假设我们有2个线程同时尝试获取锁:
T1: test_and_set -> ret false
T2: test_and_set -> ret false
Run Code Online (Sandbox Code Playgroud)
这种情况应该是可能的,因为我们有两个彼此之间acquire没有任何sync with关系的操作.是的,在我们解锁了互斥锁之后,我们进行了release一项后续操作,release sequence生活变得丰富多彩,每个人都很开心.但为什么在release sequence前进之前它是安全的?
由于很多人都提到了这个实现,我认为它应该可以正常工作.那我错过了什么?
更新1:
我完全理解操作是原子的,操作之间的操作lock并unlock不能超出临界区.这不是问题.问题是我没有看到上面的代码如何防止2个互斥锁同时进入临界区.为了防止它发生,应该在2 秒之间的关系之前发生lock.有人能告诉我,使用C++标准概念,代码是完全安全的吗?
更新2:
好吧,我相信,我们接近正确的答案.我在标准中找到了以下内容:
[atomics.order]第11条
原子读 - 修改 - 写操作应始终读取与读 - …
在x86架构上,存储到同一内存位置的总订单有,例如,请参阅此视频.C++ 11内存模型有哪些保证?
更确切地说,在
-- Initially --
std::atomic<int> x{0};
-- Thread 1 --
x.store(1, std::memory_order_release);
-- Thread 2 --
x.store(2, std::memory_order_release);
-- Thread 3 --
int r1 = x.load(std::memory_order_acquire);
int r2 = x.load(std::memory_order_acquire);
-- Thread 4 --
int r3 = x.load(std::memory_order_acquire);
int r4 = x.load(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
结果r1==1, r2==2, r3==2, r4==1是否允许(在x86以外的某些架构上)?如果我要更换所有memory_order的东西std::memory_order_relaxed怎么办?
有一对夫妇在这个网站,询问使用是否问题volatile变量原子/多线程访问是可能的:见这里,这里,还是这里的例子.
现在,符合C(++)标准的答案显然没有.
但是,在Windows和Visual C++编译器上,情况似乎并不那么清楚.
微软特定
声明为volatile的对象是(...)
- 对volatile对象的写入(volatile write)具有Release语义; 对全局或静态对象的引用?在编译二进制文件中的易失性写入之前,将在写入指令序列中的易失性对象之前发生这种情况.
- 读取volatile对象(volatile read)具有Acquire语义; 对全局或静态对象的引用 ?在读取编译二进制文件中的易失性读取之后,将在读取指令序列中的易失性存储器之后发生这种情况.
这允许volatile对象用于多线程应用程序中的内存锁定和释放.
[强调我的]
现在,读到这一点,在我看来,MS编译器将处理一个易变量变量,就像std::atomic即将推出的C++ 11标准一样.
然而,在对我的回答的评论中,用户Hans Passant写道:"那篇MSDN文章非常不幸,这是错误的.你不能用volatile来实现锁定,甚至不能使用微软的版本.(...)"
请注意:MSDN中给出的示例看起来很可疑,因为您通常无法在没有原子交换的情况下实现锁定.(正如亚历克斯所指出的那样)这仍然留下了问题.有关此MSDN文章中给出的其他信息的有效性,特别是对于此处和此处的用例.)
此外,还有Interlocked*函数的文档,尤其是InterlockedExchange带有volatile(!?)变量并进行原子读取和写入.(注意我们在SO上有一个问题 - 何时应该使用InterlockedExchange? - 没有权威性地回答是否需要这个函数来进行只读或只写原子访问.)
更重要的是,volatile上面引用的文档以某种方式暗示"全局或静态对象",我认为"真正的" 获取/释放语义应该适用于所有值. …
在大多数情况下,我写了一些无锁代码,可以在本地读取时正常工作.
本地旋转读取内存是否必然意味着我必须在旋转读取之前始终插入内存屏障?
(为了验证这一点,我设法生成一个读写器组合,导致读者永远不会看到写入的值,在某些非常特定的条件下 - 专用CPU,连接到CPU的进程,优化器一直向上,没有其他工作在循环中完成 - 所以箭头指向那个方向,但我不完全确定旋转内存屏障的成本.)
如果在缓存的存储缓冲区中没有任何内容被刷新,那么旋转内存屏障的成本是多少?即,所有过程都在做(在C中)
while ( 1 ) {
__sync_synchronize();
v = value;
if ( v != 0 ) {
... something ...
}
}
Run Code Online (Sandbox Code Playgroud)
我是否正确地假设它是免费的并且它不会阻碍任何流量的内存总线?
另一种方法是问:内存屏障是否会执行任何操作:刷新存储缓冲区,对其应用失效,并阻止编译器在其位置重新排序读/写?
反汇编,__ sync_synchronize()似乎转化为:
lock orl
Run Code Online (Sandbox Code Playgroud)
从英特尔手册(同样模糊的新手):
Volume 3A: System Programming Guide, Part 1 -- 8.1.2
Bus Locking
Intel 64 and IA-32 processors provide a LOCK# signal that
is asserted automatically during certain critical memory
operations to lock the system bus or equivalent link.
While this output signal is asserted, requests …Run Code Online (Sandbox Code Playgroud) 是否存在即使使用缓存刷新也会实现内存屏障的arch?我读到内存屏障仅影响CPU重新排序,但我读取了与内存屏障相关的语句:确保所有cpu都会看到值...,但对我来说这意味着缓存刷新/失效.
c++ ×5
volatile ×3
atomic ×2
c ×2
c++11 ×2
concurrency ×2
spinlock ×2
barrier ×1
caching ×1
lock-free ×1
memory-model ×1
stdatomic ×1
visual-c++ ×1