C++ C+中的确切规则是什么+;内存模型在获取操作之前是否阻止重新排序?
我对以下代码中的操作顺序有疑问:C++ C+中的确切规则是什么+;内存模型在获取操作之前是否阻止重新排序?,c++,language-lawyer,atomic,stdatomic,memory-barriers,C++,Language Lawyer,Atomic,Stdatomic,Memory Barriers,我对以下代码中的操作顺序有疑问: 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);
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);
}
现在,在同时执行
thread1
和thread2
之后,是否可以使和r1
和r2
都等于0?如果不是,哪些C++规则阻止了?在原始版本中,< P>,可以看到<代码> R1= 0和& R2==0 < /COD>,因为在读取之前,没有必要将存储转发到另一个线程。这不是对任一线程的操作进行重新排序,而是读取过时缓存
Thread 1's cache | Thread 2's cache
x == 0; | x == 0;
y == 0; | y == 0;
y.exchange(1, std::memory_order_acq_rel); // Thread 1
x.exchange(1, std::memory_order_acq_rel); // Thread 2
线程1上的释放被线程2忽略,反之亦然。在抽象机器中,线程上的x
和y
值不一致
Thread 1's cache | Thread 2's cache
x == 0; // stale | x == 1;
y == 1; | y == 0; // stale
r1 = x.load(std::memory_order_relaxed); // Thread 1
r2 = y.load(std::memory_order_relaxed); // Thread 2
您需要更多的线程来获得acquire/release对的“违反因果关系”,因为正常的排序规则与“在中变得可见的副作用”规则相结合,强制至少一个load
s查看1
在不丧失一般性的情况下,我们假设线程1首先执行
Thread 1's cache | Thread 2's cache
x == 0; | x == 0;
y == 0; | y == 0;
y.exchange(1, std::memory_order_acq_rel); // Thread 1
Thread 1's cache | Thread 2's cache
x == 0; | x == 0;
y == 1; | y == 1; // sync
线程1上的释放与线程2上的获取形成一对,抽象机器在两个线程上描述了一致的y
r1 = x.load(std::memory_order_relaxed); // Thread 1
x.exchange(1, std::memory_order_acq_rel); // Thread 2
r2 = y.load(std::memory_order_relaxed); // Thread 2
<>标准不定义C++内存模型,具体是如何在具有特定排序参数的原子操作周围排序。 相反,对于acquire/release排序模型,它定义了形式化关系,例如“synchronizes with”和“before”,用于指定如何在线程之间同步数据 N4762,§29.4.2-[原子秩序] 对原子对象M执行释放操作的原子操作A与对M执行获取操作的原子操作B同步 并从以A为首的释放序列中的任何副作用中获取其值 在§6.8.2.1-9中,该标准还规定,如果存储a与负载B同步,则在线程间之前排序的任何内容“发生在”之前,在B之后排序的任何内容 在您的第二个示例中没有建立“与同步”(因此线程间发生在之前)关系(第一个更弱),因为缺少运行时关系(检查加载返回值)。
但是,即使您确实检查了返回值,它也不会有帮助,因为
交换操作实际上不会“释放”任何内容(即,在这些操作之前没有对内存操作进行排序)。
不要让原子加载操作“获取”任何东西,因为加载后没有操作排序
因此,根据标准,两个示例中荷载的四种可能结果(包括0)均有效。
事实上,该标准给出的保证在所有操作上都不强于内存\u顺序
如果要在代码中排除0结果,则所有4个操作都必须使用std::memory\u order\u seq\u cst
。这保证了所涉及操作的总顺序。为了在两个线程之间创建同步点,我们需要一些原子对象M
,在两个操作中都是相同的
一种原子操作,它对一个
原子对象M
与原子操作同步B
对M
执行获取操作并从任何
以A
为首的发布序列中的副作用
或更详细地说:
如果线程A
中的原子存储被标记为memory\u order\u release
来自同一变量的线程B
中的原子负载被标记
内存\u顺序\u获取
,所有内存写入(非原子和松弛)
原子的)在原子存储之前发生的
线程A
,在线程B
中会出现明显的副作用。那个
即,一旦原子加载完成,线程B
将保证
查看线程写入内存的所有内容
同步仅在线程之间建立
以及获取相同的原子变量
这里的同步点位于M
存储释放和加载获取(从存储释放中获取值!)。因此,在线程
中存储N=u
(在M
上的存储释放之前)在B
中可见(N==u
)在相同M
上加载后
例如:
atomic<int> x, y;
int r1, r2;
void thread_A() {
y.exchange(1, memory_order_acq_rel);
r1 = x.load(memory_order_acquire);
}
void thread_B() {
x.exchange(1, memory_order_acq_rel);
r2 = y.load(memory_order_acquire);
}
假设r1==0
对任何特定原子变量的所有修改都是以总计的形式进行的
特定于此原子变量的顺序
我们对y
进行了两次修改:[Ay]
和[By]
。因为r1==0
这意味着[Ay]
发生在[By]
之前,总修改顺序为y
。从此-[By]
读取[Ay]
存储的值。因此,我们有下一步:
A
写入x
-[Ax]
A
do store release[Ay]
至y
在此之后(acq\u rel包括release,
交换(包括存储)
B
从y
([By]
存储的值[Ay]
- 原子负载获取(在
y
上)完成后,线程B
将被激活
保证看到线程A
之前写入内存的所有内容
商店放行(
N = u | if (M.load(acquire) == v) :[B]
[A]: M.store(v, release) | assert(N == u)
atomic<int> x, y;
int r1, r2;
void thread_A() {
y.exchange(1, memory_order_acq_rel);
r1 = x.load(memory_order_acquire);
}
void thread_B() {
x.exchange(1, memory_order_acq_rel);
r2 = y.load(memory_order_acquire);
}
atomic<int> x, y;
int r1, r2;
void thread_A()
{
x.exchange(1, memory_order_acq_rel); // [Ax]
r1 = y.exchange(1, memory_order_acq_rel); // [Ay]
}
void thread_B()
{
y.exchange(1, memory_order_acq_rel); // [By]
r2 = x.exchange(1, memory_order_acq_rel); // [Bx]
}
atomic<int> x, y;
int r1, r2;
void thread_A()
{
x.store(1, memory_order_relaxed); // [A1]
atomic_thread_fence(memory_order_acq_rel); // [A2]
r1 = y.exchange(1, memory_order_relaxed); // [A3]
}
void thread_B()
{
y.store(1, memory_order_relaxed); // [B1]
atomic_thread_fence(memory_order_acq_rel); // [B2]
r2 = x.exchange(1, memory_order_relaxed); // [B3]
}
[A1]: x = 1 | if (y.load(relaxed) == 1) :[B1]
[A2]: ### release ### | ### acquire ### :[B2]
[A3]: y.store(1, relaxed) | assert(x == 1) :[B3]
foo:
lwsync # with seq_cst exchange this is full sync, not just lwsync
# gone if we use exchage with mo_acquire or relaxed
# so this barrier is providing release-store ordering
li %r9,1
.L2:
lwarx %r10,0,%r4 # load-linked from 0(%r4)
stwcx. %r9,0,%r4 # store-conditional 0(%r4)
bne %cr0,.L2 # retry if SC failed
isync # missing if we use exchange(1, mo_release) or relaxed
ld %r3,0(%r3) # 64-bit load double-word of *a
cmpw %cr7,%r3,%r3
bne- %cr7,$+4 # skip over the isync if something about the load? PowerPC is weird
isync # make the *a load a load-acquire
blr
thread1():
adrp x0, .LANCHOR0
mov w1, 1
add x0, x0, :lo12:.LANCHOR0
.L2:
ldaxr w2, [x0] @ load-linked with acquire semantics
stlxr w3, w1, [x0] @ store-conditional with sc-release semantics
cbnz w3, .L2 @ retry until exchange succeeds
add x1, x0, 8 @ the compiler noticed the variables were next to each other
ldar w1, [x1] @ load-acquire
str w1, [x0, 12] @ r1 = load result
ret
r1 = x.load(std::memory_order_acquire);