微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

是否使用 std::atomic fetch_add 的结果来索引数组仍然是原子的?

如何解决是否使用 std::atomic fetch_add 的结果来索引数组仍然是原子的?

所以我想尝试使用 fetch_addfetch_sub 原子指令实现固定大小的等待空闲堆栈。假设我有一个基本的堆栈,有两个操作,push 和 pop。

struct WaitFreeStack {
    std::atomic<size_t> cur;
    std::atomic<int>* buf;

    void push(int i) {
        buf[cur.fetch_add(1)].store(i);
    }

    int pop() {
        return buf[cur.fetch_sub(1) - 1].load();
    }
};

我的问题是,操作是 B[X] 形式的,其中 B 是一个数组,X 是一个整数原子?例如,在我的示例中,是否有可能在对 fetch_add 方法push() 调用执行之后,并且在执行 B[X] 之前,整个 pop 和 {可能会在单独的线程中执行 {1}},导致 push 覆盖另一个 push ?

解决方法

是 B[X] 形式的操作,其中 B 是一个数组,X 是一个整数原子?

没有

以我的示例为例,是否有可能在执行 push() 方法的 fetch_add 调用之后,并且在执行 B[X] 之前,可以在单独的线程中执行整个 pop 和 push,从而导致一个推送覆盖另一个推送?

是的。


你的例子可以等同于:

void push(int i) {
    size_t index = cur.fetch_add(1);
    // execution time interval
    buf[index].store(i);
}

int pop() {
    size_t index = cur.fetch_sub(1) - 1;
    // execution time interval
    return buf[index].load();
}

上面两个注释位置都会有一个执行时间间隔,虽然时间间隔非常非常短,但是如果另一个线程调用pushpop并在这个时候完成调用,它绝对不安全。


实现线程安全容器的最简单方法是使用 std::mutex,还有一些无锁实现(如 boost)。

,

B[X] 不是原子的。但即使它是原子的,也无济于事。

问题在于您有表达式 多个原子操作。虽然操作是原子的,但整个表达式不是原子的。

或者:包含多个原子操作的表达式不需要是原子的。

这里的类不变量应该是 cur 指向 buf 中的当前对象。 但是这个不变量在 2 个原子操作 fetch_addstore 之间被破坏了。

如果 B[X] 是原子的(不是),那么推送的原子操作序列如下:

X = cur.fetch_add(1);  // atomic
// 1. dT
ref = B[X];             // let's assume atomic
// 2. dT
ref.store(i);           // atomic

例如在时间间隔 2.dT 中,想象第二个线程弹出 2 个项目,第三个线程推送 1 个项目,所有这些都在 ref.store(i) 之前执行。此时,ref 引用下的值会发生变化。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。