标准规定,轻松的原子操作不是同步操作。但是,其他线程看不到的操作结果的原子性是什么。
那么,示例这里不会给出预期的结果,对吗?
我通过同步理解的是,具有这种特性的操作的结果将被所有线程看到。
也许我不明白同步意味着什么。我逻辑上的漏洞在哪里?
发布于 2019-03-20 20:53:23
编译器和CPU可以重新排序内存访问。它是以此类推,它假设一个单线程进程。
在多线程程序中,内存顺序参数指定如何围绕原子操作对内存访问进行排序。这是与原子性方面本身分离的原子操作的同步方面(“获取-释放语义”):
int x = 1;
std::atomic<int> y = 1;
// Thread 1
x++;
y.fetch_add(1, std::memory_order_release);
// Thread 2
while ((y.load(std::memory_order_acquire) == 1)
{ /* wait */ }
std::cout << x << std::endl; // x is 2 now然而,在轻松的记忆顺序中,我们只得到原子性,但没有排序:
int x = 1;
std::atomic<int> y = 1;
// Thread 1
x++;
y.fetch_add(1, std::memory_order_relaxed);
// Thread 2
while ((y.load(std::memory_order_relaxed) == 1)
{ /* wait */ }
std::cout << x << std::endl; // x can be 1 or 2, we don't know事实上,正如Herb在他出色的atomic<> weapons演讲中所解释的那样,memory_order_relaxed使得多线程程序很难推理,并且应该只在非常具体的情况下使用,在任何线程中,原子操作和它之前或之后的任何其他操作之间都不存在依赖关系(很少有这种情况)。
发布于 2019-03-20 20:15:13
是的,标准是正确的。松弛原子不是同步操作,因为只保证操作的原子性。
例如,
int k = 5;
void foo() {
k = 10;
}
int baz() {
return k;
}在存在多个线程的情况下,行为是未定义的,因为它公开了争用条件。在实践中,在某些体系结构上,baz的调用者可能不会看到10,no 5,但会看到其他一些不确定的值。它常被称为撕破或肮脏的阅读。
如果使用轻松的原子负载和存储,baz将保证返回5或10,因为没有数据竞争。
值得注意的是,出于实际目的,Intel芯片及其非常强大的内存模型使轻松原子成为此公共体系结构中的一个节点(这意味着它没有额外的成本),因为负载和存储在硬件级别上是原子的。
发布于 2019-03-20 20:27:09
假设我们有
std::atomic<int> x = 0;
// thread 1
foo();
x.store(1, std::memory_order_relaxed);
// thread 2
assert(x.load(std::memory_order_relaxed) == 1);
bar();首先,不能保证线程2将遵守值1(即断言可能触发)。但是,即使线程2确实观察到值1,而线程2正在执行bar(),它也可能不会观察到foo()在线程1中产生的副作用。如果foo()和bar()访问相同的非原子变量,则可能会发生数据争用。
现在假设我们将示例更改为:
std::atomic<int> x = 0;
// thread 1
foo();
x.store(1, std::memory_order_release);
// thread 2
assert(x.load(std::memory_order_acquire) == 1);
bar();线程2仍然不能保证观察值1;毕竟,加载可能发生在存储之前。但是,在这种情况下,如果线程2观察到值1,那么线程1中的存储与线程2中的加载同步。这意味着在线程1中存储之前的所有顺序发生在线程2加载后的所有排序之前。因此,bar()将看到foo()产生的所有副作用,如果它们都访问相同的非原子变量,则不会发生数据争用。
因此,正如您所看到的,x上操作的同步属性没有告诉您x发生了什么。相反,同步对两个线程中的周围操作强制排序。(因此,在链接示例中,结果总是5,不依赖于内存排序;fetch-add操作的同步属性不会影响fetch-add操作本身的效果。)
https://stackoverflow.com/questions/55269288
复制相似问题