C++23中仅将IRIW测试的负载操作改为seq_cst是否可避免异常结果?
嘿,这个问题问到点子上了,咱们先把完整的测试场景补全再唠——经典IRIW测试里除了你给出的两个读线程,还有两个负责写入的线程:一个用relaxed语义把x设为1,另一个用relaxed语义把y设为1,完整代码是这样的:
void writer_x(atomic_int *x) { x->store(1, memory_order_relaxed); } void writer_y(atomic_int *y) { y->store(1, memory_order_relaxed); } void reader0(atomic_int *x, atomic_int *y) { int l0x = x->load(memory_order_seq_cst); int l0y = y->load(memory_order_seq_cst); } void reader1(atomic_int *x, atomic_int *y) { int l1y = y->load(memory_order_seq_cst); int l1x = x->load(memory_order_seq_cst); }
先回忆下全relaxed语义时的异常结果:reader0读到x=1、y=0,reader1读到y=1、x=0,这种各说各话的矛盾情况在全松弛规则下是允许的。但现在把所有读操作改成seq_cst,还能出现这种异常吗?
答案是绝对不能,这种矛盾结果会被彻底禁止,核心原因就在于seq_cst的核心语义:所有seq_cst操作(这里就是两个读线程的四个负载)必须遵循一个全局统一的线性总序,而且每个线程自身的程序顺序在这个总序里是严格保持的。
咱们来推演下如果异常结果发生会有什么逻辑矛盾:
- 要是reader0读到
x=1,说明写x的relaxed存储操作,在reader0的x.load之前就已经完成;而它读到y=0,说明写y的relaxed存储还没发生在它的y.load之前。 - 要是reader1读到
y=1,说明写y的存储在reader1的y.load之前完成;而它读到x=0,说明写x的存储还没发生在它的x.load之前。
现在把这些关系套进seq_cst的全局总序里:
reader0的x.load必须在它的y.load之前(程序顺序要求),reader1的y.load必须在它的x.load之前(程序顺序要求)。把刚才的推导连起来就会形成一个循环:
写x的存储 → reader0的x.load → reader0的y.load → 写y的存储 → reader1的y.load → reader1的x.load → 写x的存储
这就形成了一个逻辑上的死循环,但seq_cst的全局总序是严格的线性顺序,根本不允许这种自相矛盾的循环存在。所以这种异常结果在C++23的规则下是不可能出现的。
哪怕写操作是relaxed也没关系——seq_cst的读操作会把它们看到的存储操作,锚定到全局总序的固定位置上,直接排除了那种各线程“各说各话”的矛盾场景。
内容来源于stack exchange




