一般地,对于int num,num++(或++num),作为读-修改-写操作中,是不是原子.但我经常看到编译器,例如GCC,为它生成以下代码(在这里尝试):
由于第5行对应于num++一条指令,我们可以得出结论,在这种情况下num++ 是原子的吗?
如果是这样,是否意味着如此生成num++可以在并发(多线程)场景中使用而没有任何数据争用的危险(例如,我们不需要制作它,std::atomic<int>并强加相关成本,因为它是无论如何原子)?
UPDATE
请注意,这个问题不是增量是否是原子的(它不是,而且是问题的开头行).它是否可以在特定场景中,即在某些情况下是否可以利用单指令性质来避免lock前缀的开销.而且,作为公认的答案约单处理器的机器,还有部分提到这个答案,在其评论和其他人谈话解释,它可以(尽管不是C或C++).
如果有两个线程访问全局变量,那么许多教程都说使变量volatile变为阻止编译器将变量缓存在寄存器中,从而无法正确更新.但是,访问共享变量的两个线程是通过互斥锁来调用保护的东西不是吗?但是在这种情况下,在线程锁定和释放互斥锁之间,代码处于一个关键部分,只有那个线程可以访问变量,在这种情况下变量不需要是volatile?
那么多线程程序中volatile的用途/目的是什么?
我想知道为什么没有编译器准备将相同值的连续写入合并到单个原子变量,例如:
#include <atomic>
std::atomic<int> y(0);
void f() {
auto order = std::memory_order_relaxed;
y.store(1, order);
y.store(1, order);
y.store(1, order);
}
Run Code Online (Sandbox Code Playgroud)
我尝试过的每个编译器都会发出三次上面的编写.什么合法的,无种族的观察者可以看到上述代码与具有单次写入的优化版本之间的差异(即,不是"假设"规则适用)?
如果变量是易变的,那么显然不适用优化.在我的情况下有什么阻止它?
这是编译器资源管理器中的代码.
我有类似的东西:
if (f = acquire_load() == ) {
... use Foo
}
Run Code Online (Sandbox Code Playgroud)
和:
auto f = new Foo();
release_store(f)
Run Code Online (Sandbox Code Playgroud)
您可以很容易地想象使用atomic with load(memory_order_acquire)和store(memory_order_release)的acquire_load和release_store的实现.但是现在如果release_store是用_mm_stream_si64实现的,这是一个非临时写入,而不是针对x64上的其他商店进行排序的?如何获得相同的语义?
我认为以下是最低要求:
atomic<Foo*> gFoo;
Foo* acquire_load() {
return gFoo.load(memory_order_relaxed);
}
void release_store(Foo* f) {
_mm_stream_si64(*(Foo**)&gFoo, f);
}
Run Code Online (Sandbox Code Playgroud)
并使用它:
// thread 1
if (f = acquire_load() == ) {
_mm_lfence();
... use Foo
}
Run Code Online (Sandbox Code Playgroud)
和:
// thread 2
auto f = new Foo();
_mm_sfence(); // ensures Foo is constructed by the time f is published to gFoo
release_store(f)
Run Code Online (Sandbox Code Playgroud)
那是对的吗?我非常肯定这里绝对需要sfence.但是那个lfence怎么样?是否需要或者简单的编译器障碍对于x64是否足够?例如asm volatile("":::"memory").根据x86内存模型,负载不会与其他负载重新排序.所以根据我的理解,只要存在编译器障碍,acquire_load()必须在if语句中的任何加载之前发生.
考虑其中的位向量N(N大)和M数字数组(M中等,通常小得多N),每个都在范围内0..N-1指示必须将向量的哪个位设置为1.后一个数组未排序.位向量只是一个整数数组,具体而言__m256i,每个__m256i结构中包含256位.
如何在多个线程中有效地分割这项工作?
首选语言是C++(MSVC++ 2017工具集v141),汇编也很棒.首选CPU是x86_64(内在函数没问题).如果有任何益处,AVX2是理想的.
代码库有一个COMPILER_BARRIER定义为的宏__asm__ volatile("" ::: "memory").宏的目的是防止编译器跨屏障重新排序读取和写入.请注意,这显然是编译器障碍,而不是处理器级别的内存障碍.
因此,这是相当便携的,因为AssemblerTemplate中没有实际的汇编指令,只有volatile和memoryclobber.因此,只要编译器遵循GCC扩展Asm语法,它就可以正常工作.尽管如此,我很好奇如果可能的话,在C++ 11 atomics API中表达它的正确方法是什么.
以下似乎可能是正确的想法:atomic_signal_fence(memory_order_acq_rel);.
我的理由是:
<atomic>API中,仅需要atomic_signal_fence并且atomic_thread_fence不需要用于操作的存储器地址.atomic_thread_fence 影响内存排序,我们不需要编译器障碍.memoryExtended Asm版本中的clobber不区分读取和写入,因此看起来我们需要获取和释放语义,因此memory_order_acq_rel似乎至少需要.memory_order_seq_cst 似乎没必要,因为我们不需要跨线程的总顺序 - 我们只对当前线程中的指令排序感兴趣.是否有可能__asm__ volatile("" ::: "memory")用C++ 11 atomics API 表达完全可移植的等价物?如果是这样,是否atomic_signal_fence使用正确的API?如果是这样,这里适当/需要什么内存顺序参数?
或者,我在这里的杂草中,有一个更好的方法来解决这个问题吗?
比如说,我Foo在线程#1中创建了一个类型的对象,并希望能够在线程#3中访问它.
我可以尝试这样的事情:
std::atomic<int> sync{10};
Foo *fp;
// thread 1: modifies sync: 10 -> 11
fp = new Foo;
sync.store(11, std::memory_order_release);
// thread 2a: modifies sync: 11 -> 12
while (sync.load(std::memory_order_relaxed) != 11);
sync.store(12, std::memory_order_relaxed);
// thread 3
while (sync.load(std::memory_order_acquire) != 12);
fp->do_something();
Run Code Online (Sandbox Code Playgroud)
Foo,更新为11sync增加到12该场景被破坏,因为线程#3旋转直到它加载12,其可能无序到达(wrt 11)并且Foo不与12一起订购(由于线程#2a中的放松操作).
这有点违反直觉,因为修改顺序sync是10→11→12
标准说(§1.10.1-6):
原子存储 - 释放与从存储中获取其值的load-acquire同步(29.3).[注意:除了在指定的情况下,读取更高的值不一定能确保可见性,如下所述.这样的要求有时会干扰有效的实施. - 尾注]
它也在(§1.10.1-5)中说:
由原子对象M上的释放操作A引导的释放序列是M的修改顺序中的副作用的最大连续子序列,其中第一操作是A,并且每个后续操作
- 由执行A的相同线程执行. ,或
- 是原子读 - 修改 - 写操作.
现在,线程#2a被修改为使用原子读 - 修改 …