基于原子量实现的Rust Barrier存在什么问题?
你的原子Barrier实现挂起的原因及修复方案
核心问题分析
你的Barrier实现存在跨轮次的竞态条件,导致部分线程永远卡在等待循环中,具体原因如下:
- 无代次区分,计数器被不同轮次操作干扰
当最后一个线程完成屏障并将done重置为0后,已经通过屏障的线程会立刻进入下一轮wait()调用,直接对done执行fetch_add(1)操作。而此时可能还有线程尚未退出上一轮的while循环,它们会看到done被重新置为非0值,误以为上一轮的屏障还未完成,从而永远卡在循环里。
举个具体场景:
- 第一轮屏障:线程10(最后一个)完成
fetch_add后将done设为0,线程1立刻退出循环,执行完println!("a")后调用wait(),把done加到1。 - 此时线程2还在执行第一轮的
while self.done.load() !=0检查,它会读到done=1,继续等待,但后续所有线程的下一轮wait()会不断把done往上加,永远不会回到0,线程2就彻底挂起了。
- 重置操作的竞态隐患
即使没有跨轮次问题,最后一个线程的store(0)操作与其他线程的load操作之间,虽然用了SeqCst内存顺序,但无法保证所有线程都能在重置后及时退出循环——不过这不是导致挂起的核心原因。
修复方案:引入代次(Generation)区分不同屏障轮次
我们需要给Barrier增加一个“代次”标记,让每个线程只等待当前轮次的屏障完成,不会被下一轮的计数操作干扰。修改后的实现如下:
use std::sync::atomic::{AtomicUsize, Ordering}; pub struct Barrier { count: AtomicUsize, generation: AtomicUsize, tids: usize, } impl Barrier { pub fn new(tids: usize) -> Barrier { Barrier { count: AtomicUsize::new(0), generation: AtomicUsize::new(0), tids, } } pub fn wait(&self) { let current_gen = self.generation.load(Ordering::SeqCst); let count = self.count.fetch_add(1, Ordering::SeqCst); // 如果是最后一个到达的线程,推进代次并重置计数 if count + 1 == self.tids { self.count.store(0, Ordering::SeqCst); self.generation.fetch_add(1, Ordering::SeqCst); } else { // 等待当前代次变化,说明屏障已完成 while self.generation.load(Ordering::SeqCst) == current_gen {} } } }
修复逻辑说明:
- 每个屏障轮次对应一个唯一的
generation值,线程进入wait()时先记录当前代次。 - 最后一个线程完成计数后,会推进代次并重置计数,其他线程通过检查代次是否变化来判断屏障是否完成,完全避免了跨轮次的计数干扰。
- 即使线程在屏障完成后立刻进入下一轮
wait(),新的计数操作会对应新的代次,不会影响上一轮未退出的线程(实际上所有线程都会在代次变化后退出循环)。
测试验证
用修改后的Barrier运行你的测试代码,所有线程会交替输出a和b,不会出现挂起情况。
内容的提问来源于stack exchange,提问作者MaiaVictor
相关产品推荐
相关产品推荐

