问题描述
所以我想尝试使用 fetch_add
和 fetch_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();
}
上面两个注释位置都会有一个执行时间间隔,虽然时间间隔非常非常短,但是如果另一个线程调用push
或pop
并在这个时候完成调用,它绝对不安全。
实现线程安全容器的最简单方法是使用 std::mutex
,还有一些无锁实现(如 boost)。
B[X]
不是原子的。但即使它是原子的,也无济于事。
问题在于您有表达式 多个原子操作。虽然操作是原子的,但整个表达式不是原子的。
或者:包含多个原子操作的表达式不需要是原子的。
这里的类不变量应该是 cur
指向 buf
中的当前对象。
但是这个不变量在 2 个原子操作 fetch_add
和 store
之间被破坏了。
如果 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
引用下的值会发生变化。