首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >原子操作怎么可能不是同步操作呢?

原子操作怎么可能不是同步操作呢?
EN

Stack Overflow用户
提问于 2019-03-20 20:02:20
回答 3查看 272关注 0票数 1

标准规定,轻松的原子操作不是同步操作。但是,其他线程看不到的操作结果的原子性是什么。

那么,示例这里不会给出预期的结果,对吗?

我通过同步理解的是,具有这种特性的操作的结果将被所有线程看到。

也许我不明白同步意味着什么。我逻辑上的漏洞在哪里?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-03-20 20:53:23

编译器和CPU可以重新排序内存访问。它是以此类推,它假设一个单线程进程。

在多线程程序中,内存顺序参数指定如何围绕原子操作对内存访问进行排序。这是与原子性方面本身分离的原子操作的同步方面(“获取-释放语义”):

代码语言:javascript
复制
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

然而,在轻松的记忆顺序中,我们只得到原子性,但没有排序:

代码语言:javascript
复制
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使得多线程程序很难推理,并且应该只在非常具体的情况下使用,在任何线程中,原子操作和它之前或之后的任何其他操作之间都不存在依赖关系(很少有这种情况)。

票数 3
EN

Stack Overflow用户

发布于 2019-03-20 20:15:13

是的,标准是正确的。松弛原子不是同步操作,因为只保证操作的原子性。

例如,

代码语言:javascript
复制
int k = 5;
void foo() {
    k = 10;
}

int baz() {
    return k;
}

在存在多个线程的情况下,行为是未定义的,因为它公开了争用条件。在实践中,在某些体系结构上,baz的调用者可能不会看到10,no 5,但会看到其他一些不确定的值。它常被称为撕破或肮脏的阅读。

如果使用轻松的原子负载和存储,baz将保证返回5或10,因为没有数据竞争。

值得注意的是,出于实际目的,Intel芯片及其非常强大的内存模型使轻松原子成为此公共体系结构中的一个节点(这意味着它没有额外的成本),因为负载和存储在硬件级别上是原子的。

票数 0
EN

Stack Overflow用户

发布于 2019-03-20 20:27:09

假设我们有

代码语言:javascript
复制
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()访问相同的非原子变量,则可能会发生数据争用。

现在假设我们将示例更改为:

代码语言:javascript
复制
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操作本身的效果。)

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55269288

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档