与 C++ 原子内存栅栏同步
Synchronization with C++ atomic memory fence
我对使用内存栅栏同步下面的代码有疑问。
std::atomic<int> a = 0;
std::atomic<int> b = 0;
void increase_b() {
std::atomic_thread_fence(std::memory_order_release);
b.store(1, std::memory_ordered_relaxed);
}
bool diff() {
int val_a = a.load(std::memory_ordered_relaxed);
int val_b = b.load(std::memory_ordered_relaxed);
return val_b > val_a;
}
void f1() {
increase_b();
std::atomic_thread_fence(std::memory_order_seq_cst);
}
void f2() {
std::atomic_thread_fence(std::memory_order_seq_cst);
bool result = diff();
}
int main() {
std::thread t1(f1);
std::thread t2(f2);
t1.join(); t2.join();
}
假设 t1 已经完成 f1
然后 t2 刚刚开始 f2
,将 t2
看到 b
增加了吗?
您的代码过于复杂。 a=0
永远不会改变,所以它 总是 读作 0。你还不如只有 atomic<int> b=0;
并且只有一个负载 return b.load
.
Assume t1 has finished f1 and then t2 just started f2, will t2 see b incremented?
除非您将 t1.join()
置于 std::thread t2(f2);
构造之前,否则您无法检测到时间是这样计算的。这将要求线程 2 中的所有内容都在线程 1 中的所有内容之后排序。(我认为即使 f1 末尾没有 seq_cst
栅栏,但这也无妨。我认为 thread.join 确保在 thread.join
)
之后线程内完成的所有内容都是可见的
但是,是的,这种排序可能是偶然发生的,然后当然会起作用。
不能保证在 C++ 术语中这甚至是一个有意义的条件。
但对于大多数(所有?)实际实施来说,这是有可能发生的事情。 thread_fence(mo_seq_cst)
将编译为一个完整的屏障,该屏障会阻塞该线程,直到存储提交(对所有线程全局可见)。因此执行不能离开 f1,直到其他线程的读取可以看到 b
的更新值。 (C++ 标准根据创建同步关系来定义排序和栅栏,而不是根据编译到刷新存储缓冲区的完整屏障来定义。该标准没有提到存储缓冲区或 StoreLoad 重新排序或任何 CPU记忆顺序的东西。)
鉴于综合条件,线程实际上是按顺序排列的。就像所有事情都在一个线程中完成一样。
diff()
中的负载未按顺序排列。彼此,因为他们都是 mo_relaxed
。但是 a
永远不会被任何线程修改,所以唯一的问题是 b.load()
是否可以在线程启动之前发生,在 f1
存储可见之前。在实际实现中,它不能因为“然后 t2 刚刚开始 f2”的意思。 如果它可以加载旧值,那么你就不能说“然后”,所以这几乎是同义反复。
负载前的 thread_fence(seq_cst)
没有任何帮助。我猜它会阻止 b.load()
使用线程启动机制重新排序。
我对使用内存栅栏同步下面的代码有疑问。
std::atomic<int> a = 0;
std::atomic<int> b = 0;
void increase_b() {
std::atomic_thread_fence(std::memory_order_release);
b.store(1, std::memory_ordered_relaxed);
}
bool diff() {
int val_a = a.load(std::memory_ordered_relaxed);
int val_b = b.load(std::memory_ordered_relaxed);
return val_b > val_a;
}
void f1() {
increase_b();
std::atomic_thread_fence(std::memory_order_seq_cst);
}
void f2() {
std::atomic_thread_fence(std::memory_order_seq_cst);
bool result = diff();
}
int main() {
std::thread t1(f1);
std::thread t2(f2);
t1.join(); t2.join();
}
假设 t1 已经完成 f1
然后 t2 刚刚开始 f2
,将 t2
看到 b
增加了吗?
您的代码过于复杂。 a=0
永远不会改变,所以它 总是 读作 0。你还不如只有 atomic<int> b=0;
并且只有一个负载 return b.load
.
Assume t1 has finished f1 and then t2 just started f2, will t2 see b incremented?
除非您将 t1.join()
置于 std::thread t2(f2);
构造之前,否则您无法检测到时间是这样计算的。这将要求线程 2 中的所有内容都在线程 1 中的所有内容之后排序。(我认为即使 f1 末尾没有 seq_cst
栅栏,但这也无妨。我认为 thread.join 确保在 thread.join
)
但是,是的,这种排序可能是偶然发生的,然后当然会起作用。
不能保证在 C++ 术语中这甚至是一个有意义的条件。
但对于大多数(所有?)实际实施来说,这是有可能发生的事情。 thread_fence(mo_seq_cst)
将编译为一个完整的屏障,该屏障会阻塞该线程,直到存储提交(对所有线程全局可见)。因此执行不能离开 f1,直到其他线程的读取可以看到 b
的更新值。 (C++ 标准根据创建同步关系来定义排序和栅栏,而不是根据编译到刷新存储缓冲区的完整屏障来定义。该标准没有提到存储缓冲区或 StoreLoad 重新排序或任何 CPU记忆顺序的东西。)
鉴于综合条件,线程实际上是按顺序排列的。就像所有事情都在一个线程中完成一样。
diff()
中的负载未按顺序排列。彼此,因为他们都是 mo_relaxed
。但是 a
永远不会被任何线程修改,所以唯一的问题是 b.load()
是否可以在线程启动之前发生,在 f1
存储可见之前。在实际实现中,它不能因为“然后 t2 刚刚开始 f2”的意思。 如果它可以加载旧值,那么你就不能说“然后”,所以这几乎是同义反复。
负载前的 thread_fence(seq_cst)
没有任何帮助。我猜它会阻止 b.load()
使用线程启动机制重新排序。