一般地,对于int num
,num++
(或++num
),作为读-修改-写操作中,是不是原子.但我经常看到编译器,例如GCC,为它生成以下代码(在这里尝试):
由于第5行对应于num++
一条指令,我们可以得出结论,在这种情况下num++
是原子的吗?
如果是这样,是否意味着如此生成num++
可以在并发(多线程)场景中使用而没有任何数据争用的危险(例如,我们不需要制作它,std::atomic<int>
并强加相关成本,因为它是无论如何原子)?
UPDATE
请注意,这个问题不是增量是否是原子的(它不是,而且是问题的开头行).它是否可以在特定场景中,即在某些情况下是否可以利用单指令性质来避免lock
前缀的开销.而且,作为公认的答案约单处理器的机器,还有部分提到这个答案,在其评论和其他人谈话解释,它可以(尽管不是C或C++).
另一周,我写了一个小线程类和一个单向消息管道,以允许线程之间的通信(每个线程两个管道,显然,用于双向通信).在我的Athlon 64 X2上一切正常,但我想知道如果两个线程都在查看相同的变量并且每个核心上的此变量的本地缓存值不同步,我是否会遇到任何问题.
我知道volatile关键字会强制变量从内存中刷新,但多核x86处理器是否有办法强制所有内核的缓存同步?这是我需要担心的事情,还是易失性和正确使用轻量级锁定机制(我使用_InterlockedExchange设置我的易失性管道变量)处理我想为多核x86 CPU编写"无锁"代码的所有情况?
我已经知道并使用了Critical Sections,Mutexes,Events等等.我主要想知道是否有x86内在函数,我不知道哪种力量或可用于强制缓存一致性.
我研究了Java内存模型并看到了重新排序问题.一个简单的例子:
boolean first = false;
boolean second = false;
void setValues() {
first = true;
second = true;
}
void checkValues() {
while(!second);
assert first;
}
Run Code Online (Sandbox Code Playgroud)
重新排序是非常不可预测和奇怪的.此外,它破坏了抽象.我认为处理器架构必须有充分的理由去做一些对程序员来说太不方便的事情. 这些原因是什么?
有很多关于如何处理重新排序的信息,但我找不到任何关于它为什么需要的信息.在任何地方,人们只会说"这是因为一些性能优势".例如,second
之前存储的性能优势是first
什么?
您能推荐一些关于此的文章,论文或书籍,或者自己解释一下吗?
java optimization multithreading cpu-architecture compiler-optimization
在英特尔优化手册似乎对存储缓冲区的数量存在于处理器的许多地方,但谈判没有谈存储缓冲区的大小.这是公共信息还是商店缓冲区的大小保留为微架构细节?
我正在研究的处理器主要是Broadwell和Skylake,但其他人的信息也不错.
另外,存储缓冲区究竟做了什么?
与我之前的问题类似,请考虑以下代码
-- Initially --
std::atomic<int> x{0};
std::atomic<int> y{0};
-- Thread 1 --
x.store(1, std::memory_order_release);
-- Thread 2 --
y.store(2, std::memory_order_release);
-- Thread 3 --
int r1 = x.load(std::memory_order_acquire); // x first
int r2 = y.load(std::memory_order_acquire);
-- Thread 4 --
int r3 = y.load(std::memory_order_acquire); // y first
int r4 = x.load(std::memory_order_acquire);
Run Code Online (Sandbox Code Playgroud)
是怪异的结果 r1==1, r2==0
,并r3==2, r4==0
有可能在C ++ 11内存模型下,这种情况下?如果我要全部替换std::memory_order_acq_rel
成该std::memory_order_relaxed
怎么办?
在x86上,这样的结果似乎是被禁止的,请参见此SO问题,但我一般是在询问C ++ 11内存模型。
奖励问题:
我们都同意,与std::memory_order_seq_cst
该怪异的结果不会在C ++ 11被允许。现在,赫伯·萨特(Herb Sutter)在他著名的- …
我看到g ++生成一个简单的mov
for x.load()
和mov
+ mfence
for x.store(y)
.考虑这个经典的例子:
#include<atomic>
#include<thread>
std::atomic<bool> x,y;
bool r1;
bool r2;
void go1(){
x.store(true);
}
void go2(){
y.store(true);
}
bool go3(){
bool a=x.load();
bool b=y.load();
r1 = a && !b;
}
bool go4(){
bool b=y.load();
bool a=x.load();
r2= b && !a;
}
int main() {
std::thread t1(go1);
std::thread t2(go2);
std::thread t3(go3);
std::thread t4(go4);
t1.join();
t2.join();
t3.join();
t4.join();
return r1*2 + r2;
}
Run Code Online (Sandbox Code Playgroud)
其中根据https://godbolt.org/z/APS4ZY go1和go2被翻译成
go1():
mov BYTE PTR x[rip], 1 …
Run Code Online (Sandbox Code Playgroud) 据我了解,当 CPU 推测性地执行一段代码时,它会在切换到推测性分支之前“备份”寄存器状态,以便如果预测结果错误(使分支无用)——寄存器状态将是安全恢复,而不会破坏“状态”。
所以,我的问题是:推测执行的 CPU 分支是否可以包含访问 RAM 的操作码?
我的意思是,访问 RAM 不是“原子”操作——如果数据当前不在 CPU 缓存中,那么从内存中读取一个简单的操作码可能会导致实际的 RAM 访问,这可能会变成一个非常耗时的操作,从 CPU 的角度来看。
如果在推测分支中确实允许这种访问,它是否仅用于读取操作?因为,我只能假设,如果一个分支被丢弃并执行“回滚”,根据它的大小恢复写操作可能会变得非常缓慢和棘手。而且,可以肯定的是,至少在某种程度上支持读/写操作,因为寄存器本身,在某些 CPU 上,据我所知,物理上位于 CPU 缓存上。
所以,也许更精确的表述是:推测执行的一段代码有什么限制?
c++ ×4
x86 ×3
assembly ×2
memory-model ×2
stdatomic ×2
atomic ×1
c ×1
c++11 ×1
concurrency ×1
cpu ×1
cpu-cache ×1
intel ×1
java ×1
multicore ×1
numa ×1
optimization ×1
performance ×1