一般地,对于int num,num++(或++num),作为读-修改-写操作中,是不是原子.但我经常看到编译器,例如GCC,为它生成以下代码(在这里尝试):
由于第5行对应于num++一条指令,我们可以得出结论,在这种情况下num++ 是原子的吗?
如果是这样,是否意味着如此生成num++可以在并发(多线程)场景中使用而没有任何数据争用的危险(例如,我们不需要制作它,std::atomic<int>并强加相关成本,因为它是无论如何原子)?
UPDATE
请注意,这个问题不是增量是否是原子的(它不是,而且是问题的开头行).它是否可以在特定场景中,即在某些情况下是否可以利用单指令性质来避免lock前缀的开销.而且,作为公认的答案约单处理器的机器,还有部分提到这个答案,在其评论和其他人谈话解释,它可以(尽管不是C或C++).
如果有两个线程访问全局变量,那么许多教程都说使变量volatile变为阻止编译器将变量缓存在寄存器中,从而无法正确更新.但是,访问共享变量的两个线程是通过互斥锁来调用保护的东西不是吗?但是在这种情况下,在线程锁定和释放互斥锁之间,代码处于一个关键部分,只有那个线程可以访问变量,在这种情况下变量不需要是volatile?
那么多线程程序中volatile的用途/目的是什么?
对于std::atomic<T>T是基本类型的任何地方:
如果我使用std::memory_order_acq_rel的fetch_xxx操作,以及std::memory_order_acquire用于load操作和std::memory_order_release对store操作盲目(我的意思是,就像重置这些功能的默认内存排序)
std::memory_order_seq_cst(用作默认值)的任何声明操作相同?std::memory_order_seq_cst在效率方面是否与使用不同?http://en.cppreference.com/w/cpp/atomic/memory_order和其他C++ 11在线参考,将memory_order_acquire和memory_order_release定义为:
这似乎允许在获取操作之前执行获取后写入,这看起来很奇怪(通常的获取/释放操作语义限制所有内存操作的移动).
相同的在线资源(http://en.cppreference.com/w/cpp/atomic/atomic_flag)表明可以使用C++原子和上面提到的宽松内存排序规则构建自旋锁互斥:
lock mutex: while (lock.test_and_set(std::memory_order_acquire))
unlock mutex: lock.clear(std::memory_order_release);
Run Code Online (Sandbox Code Playgroud)
有了这个锁定/解锁的定义,如果确实以这种方式定义了memory_order_acquire/release,那么下面的简单代码就不会被破坏(即,不禁止对获取后写入进行重新排序):
Thread1:
(0) lock
(1) x = 1;
(2) if (x != 1) PANIC
(3) unlock
Thread2:
(4) lock
(5) x = 0;
(6) unlock
Run Code Online (Sandbox Code Playgroud)
以下执行是否可行:(0)锁定,(1)x = 1,(5)x = 0,(2)PANIC?我错过了什么?
我目前正在阅读Anthony Williams的C++ Concurrency in Action.他的一个列表显示了这段代码,他声明z != 0可以解雇的断言.
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x()
{
x.store(true,std::memory_order_release);
}
void write_y()
{
y.store(true,std::memory_order_release);
}
void read_x_then_y()
{
while(!x.load(std::memory_order_acquire));
if(y.load(std::memory_order_acquire))
++z;
}
void read_y_then_x()
{
while(!y.load(std::memory_order_acquire));
if(x.load(std::memory_order_acquire))
++z;
}
int main()
{
x=false;
y=false;
z=0;
std::thread a(write_x);
std::thread b(write_y);
std::thread c(read_x_then_y);
std::thread d(read_y_then_x);
a.join();
b.join();
c.join();
d.join();
assert(z.load()!=0);
}
Run Code Online (Sandbox Code Playgroud)
所以我能想到的不同执行路径是这样的:
1)
Run Code Online (Sandbox Code Playgroud)Thread a (x is now true) Thread c (fails to increment z) Thread b (y …
我在以下代码中有关于操作顺序的问题:
std::atomic<int> x;
std::atomic<int> y;
int r1;
int r2;
void thread1() {
y.exchange(1, std::memory_order_acq_rel);
r1 = x.load(std::memory_order_relaxed);
}
void thread2() {
x.exchange(1, std::memory_order_acq_rel);
r2 = y.load(std::memory_order_relaxed);
}
Run Code Online (Sandbox Code Playgroud)
鉴于std::memory_order_acquirecppreference页面上的描述(https://en.cppreference.com/w/cpp/atomic/memory_order),
具有此内存顺序的加载操作会对受影响的内存位置执行获取操作:在此加载之前,不能对当前线程中的读取或写入进行重新排序.
很明显,r1 == 0 && r2 == 0在跑步thread1和thread2同时之后永远不会有结果.
但是,我在C++标准中找不到任何措辞(现在查看C++ 14草案),这保证了两个宽松的加载不能与获取 - 释放交换重新排序.我错过了什么?
编辑:正如评论中所建议的那样,实际上可以使r1和r2都等于零.我已经更新了程序以使用load-acquire,如下所示:
std::atomic<int> x;
std::atomic<int> y;
int r1;
int r2;
void thread1() {
y.exchange(1, std::memory_order_acq_rel);
r1 = x.load(std::memory_order_acquire);
}
void thread2() {
x.exchange(1, std::memory_order_acq_rel);
r2 = y.load(std::memory_order_acquire);
}
Run Code Online (Sandbox Code Playgroud)
现在是有可能得到两个和r1以及 …
在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怎么办?
Java 内存可见性文档说:
在每次后续读取同一字段之前,会发生对易失性字段的写入.
我很困惑多线程背景下的后续意义.这句话是否意味着所有处理器和内核都有一些全局时钟.那么例如我在某个线程的循环c1中为变量赋值,然后第二个线程能够在后续循环c1 + 1中看到该值?
我想编写可移植代码(Intel、ARM、PowerPC...)来解决一个经典问题的变体:
Initially: X=Y=0
Thread A:
X=1
if(!Y){ do something }
Thread B:
Y=1
if(!X){ do something }
Run Code Online (Sandbox Code Playgroud)
其中目标是避免两个线程都在做的情况something。(如果两者都没有运行也没关系;这不是只运行一次的机制。)如果您发现我下面的推理中有一些缺陷,请纠正我。
我知道,我可以通过memory_order_seq_cstatomic stores 和loads实现目标,如下所示:
std::atomic<int> x{0},y{0};
void thread_a(){
x.store(1);
if(!y.load()) foo();
}
void thread_b(){
y.store(1);
if(!x.load()) bar();
}
Run Code Online (Sandbox Code Playgroud)
这实现了目标,因为
{x.store(1), y.store(1), y.load(), x.load()}事件必须有一些单一的总顺序,它必须与程序顺序“边缘”一致:
x.store(1) “在TO之前” y.load()y.store(1) “在TO之前” x.load()如果foo()被调用,那么我们有额外的优势:
y.load() “之前读取值” y.store(1)如果bar()被调用,那么我们有额外的优势:
x.load() “之前读取值” x.store(1)所有这些边组合在一起将形成一个循环:
x.store(1)“在TO之前”“在TO之前y.load()读取值” y.store(1)“在TO之前” x.load()“读取之前值”x.store(true)
这违反了订单没有周期的事实。
我故意使用非标准术语“在 TO …
在英特尔64和IA-32架构软件开发人员手册说,大约由单一处理器的行动("在P6更多最近的处理器系列内存排序和"第8.2.2节)重新排序如下:
读取可以使用较旧的写入到不同位置进行重新排序,但不能使用较旧的写入到同一位置.
接下来讨论与早期处理器相比放松的点时,它说:
存储缓冲区转发,当读取将写入传递到同一存储器位置时.
据我所知,"存储缓冲区转发"并未在任何地方精确定义(也不是"通过").读取将写入传递到同一位置是什么意思,因为上面说它不能通过写入同一位置来重新排序?