x86平台下compare_exchange_weak()用acquire-release致竞态问题咨询
读写锁中compare_exchange_weak的内存序正确用法
你碰到的问题根源是错误地给compare_exchange_weak指定了内存序参数——不是不能用更宽松的内存屏障,而是用错了语义组合,才被ThreadSanitizer检测出竞态。
错误用法的核心问题
你代码里给compare_exchange_weak传的std::memory_order_acquire(成功序)和std::memory_order_release(失败序)完全不符合锁的语义逻辑:
- 写锁获取(writer_lock):
失败时只是重新加载原子变量的当前值,根本不需要release语义(release是给写入操作做可见性保证的,失败时是读取操作)。错误的release语义会导致当前线程看不到其他线程的修改,引发竞态。
成功时确实需要acquire语义(保证后续临界区的++counter不会被重排到锁获取之前),但失败序用release完全是南辕北辙。 - 写锁释放(writer_unlock):
成功时需要的是release语义(保证临界区的所有操作都完成后才释放锁),但你用了acquire,这会导致临界区的操作可能被重排到解锁之后,直接触发竞态。
失败时同样不需要release语义,应该用读取类的内存序。
正确的宽松内存序用法
我们完全可以用更宽松的内存序,只要严格对应锁的获取/释放语义:
- writer_lock:
初始的load可以用memory_order_relaxed——因为后续的compare_exchange会验证值的有效性,失败时会自动把prev_readers更新为最新值,没必要加严格屏障。
compare_exchange_weak的成功序用memory_order_acquire(保证临界区代码一定在锁获取之后执行),失败序用memory_order_relaxed(只是重新加载值,不需要额外屏障)。 - writer_unlock:
初始的load同样用memory_order_relaxed。
compare_exchange_weak的成功序用memory_order_release(保证临界区代码全部执行完才会释放锁),失败序用memory_order_relaxed。
修正后的关键代码片段:
void writer_lock() { while (true) { uint32_t prev_readers = _reader_count.load(std::memory_order_relaxed); if (prev_readers == 0) { if (_reader_count.compare_exchange_weak(prev_readers, HAS_WRITER, std::memory_order_acquire, std::memory_order_relaxed)) { return; } } } } void writer_unlock() { while (true) { uint32_t prev_readers = _reader_count.load(std::memory_order_relaxed); if (prev_readers == HAS_WRITER) { if (_reader_count.compare_exchange_weak(prev_readers, 0, std::memory_order_release, std::memory_order_relaxed)) { return; } } } }
为什么默认seq_cst没问题
默认的std::memory_order_seq_cst是最严格的内存序,所有原子操作都会形成全局一致的执行顺序,相当于自动给所有操作加上了完整的屏障。所以你注释掉的默认参数代码不会出现竞态,但性能会比正确的宽松内存序差不少。
内容的提问来源于stack exchange,提问作者intrigued_66
相关产品推荐
相关产品推荐

