news 2026/9/27 11:30:20

Rust std::sync::Barrier 栅栏详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Rust std::sync::Barrier 栅栏详解

Rust std::sync::Barrier 栅栏详解

    • 1、 引言
    • 2、 Barrier 是什么
      • 2.1、 核心概念
      • 2.2 、与其它同步原语的区别
    • 3、Barrier 的基本用法
      • 3.1、 创建 Barrier
      • 3.2、 等待到达
    • 4、 wait() 的返回值
    • 5、 Barrier 的复用
    • 6、 实战示例:并行计算求和
    • 7、注意事项与常见陷阱
      • 7.1、 参与者数量必须匹配
      • 7.2、 线程 panic 会导致死锁
      • 7.3、 不要在线程池中滥用
      • 7.4、 与 Condvar 的区别
    • 8、总结
  • 二、示例代码

1、 引言

在多线程编程中,线程之间的同步是一个核心问题。Rust 标准库提供了多种同步原语,如Mutex、RwLock、Condvar等,而std::sync::Barrier(栅栏)则是其中一种非常实用但常被忽视的同步工具。

Barrier允许一组线程相互等待,直到所有线程都到达某个执行点后,再一起继续往下执行。这种「会合点」机制在并行计算、分阶段任务处理等场景中非常有用。

本文将深入剖析Barrier的原理、用法、注意事项,并通过多个实战示例帮助你彻底掌握它。

2、 Barrier 是什么

Barrier是 Rust 标准库提供的一种同步原语,用于让多个线程在某个执行点相互等待,直到所有参与的线程都到达该点后,才一起放行继续执行。

2.1、 核心概念

  • 参与者(Party):参与同步的线程数量,在创建Barrier时指定。
  • 到达(Arrive):线程调用wait()方法表示自己已到达栅栏点。
  • 放行(Release):当最后一个参与者到达后,所有等待的线程被同时唤醒,一起继续执行。

2.2 、与其它同步原语的区别

同步原语作用典型场景
Mutex互斥访问共享数据保护临界区
Condvar条件等待与唤醒生产者-消费者
Barrier多线程会合同步分阶段并行计算

Mutex和Condvar解决的是「数据竞争」和「条件等待」问题,而Barrier解决的是「多线程步调一致」的问题。

3、Barrier 的基本用法

3.1、 创建 Barrier

使用Barrier::new(n)创建一个需要n个参与者到达的栅栏:

usestd::sync::Barrier;// 创建一个需要 3 个线程到达的栅栏letbarrier=Barrier::new(3);

3.2、 等待到达

线程调用wait()方法表示自己已到达栅栏点:

usestd::sync::{Arc,Barrier};usestd::thread;fnmain(){letbarrier=Arc::new(Barrier::new(3));letmuthandles=vec![];foriin0..3{letbarrier=Arc::clone(&barrier);lethandle=thread::spawn(move||{println!("线程 {} 开始工作",i);thread::sleep(std::time::Duration::from_millis(i*100));// 到达栅栏,等待其它线程barrier.wait();println!("线程 {} 通过栅栏,继续执行",i);});handles.push(handle);}forhandleinhandles{handle.join().unwrap();}}

运行结果(顺序可能略有不同):

线程 0 开始工作 线程 1 开始工作 线程 2 开始工作 线程 2 通过栅栏,继续执行 线程 0 通过栅栏,继续执行 线程 1 通过栅栏,继续执行

可以看到,三个线程各自完成「开始工作」后,都在栅栏处等待,直到最后一个线程到达后,才一起继续执行。

4、 wait() 的返回值

Barrier::wait()返回一个BarrierWaitResult,它有一个is_leader()方法,用于判断当前线程是否是「领导者」——即最后一个到达栅栏的线程:

usestd::sync::{Arc,Barrier};usestd::thread;fnmain(){letbarrier=Arc::new(Barrier::new(3));letmuthandles=vec![];foriin0..3{letbarrier=Arc::clone(&barrier);lethandle=thread::spawn(move||{letresult=barrier.wait();ifresult.is_leader(){println!("线程 {} 是最后一个到达的(领导者)",i);}else{println!("线程 {} 不是领导者",i);}});handles.push(handle);}forhandleinhandles{handle.join().unwrap();}}

输出结果:

线程 2 是最后一个到达的(领导者) 线程 0 不是领导者 线程 1 不是领导者

is_leader()在需要「由某个线程执行一次性的初始化/汇总工作」时非常有用,例如分阶段计算中由领导者汇总各线程的结果。

5、 Barrier 的复用

Barrier的一个重要特性是可复用。当所有线程通过栅栏后,Barrier会自动重置,可以继续用于下一轮同步。这使得它非常适合分阶段(多轮)并行任务:

usestd::sync::{Arc,Barrier};usestd::thread;fnmain(){letbarrier=Arc::new(Barrier::new(3));letrounds=3;letmuthandles=vec![];foriin0..3{letbarrier=Arc::clone(&barrier);lethandle=thread::spawn(move||{forroundin0..rounds{// 模拟阶段工作println!("线程 {} 执行第 {} 轮工作",i,round);thread::sleep(std::time::Duration::from_millis(50));// 等待所有线程完成本轮barrier.wait();}});handles.push(handle);}forhandleinhandles{handle.join().unwrap();}}

输出结果:

线程 0 执行第 0 轮工作 线程 1 执行第 0 轮工作 线程 2 执行第 0 轮工作 线程 0 执行第 1 轮工作 线程 1 执行第 1 轮工作 线程 2 执行第 1 轮工作 线程 0 执行第 2 轮工作 线程 1 执行第 2 轮工作 线程 2 执行第 2 轮工作

可以看到,每一轮中所有线程都完成工作后,才会统一进入下一轮。这正是Barrier复用的典型场景。

6、 实战示例:并行计算求和

下面通过一个完整的实战示例,展示Barrier在并行计算中的应用——多个线程分别计算部分和,最后汇总:

usestd::sync::{Arc,Barrier,Mutex};usestd::thread;fnmain(){letdata:Vec<i32>=(1..=100).collect();letnum_threads=4;letchunk_size=data.len()/num_threads;letbarrier=Arc::new(Barrier::new(num_threads));letpartial_sums=Arc::new(Mutex::new(vec![0i32;num_threads]));letmuthandles=vec![];fortin0..num_threads{letdata=data.clone();letbarrier=Arc::clone(&barrier);letpartial_sums=Arc::clone(&partial_sums);lethandle=thread::spawn(move||{letstart=t*chunk_size;letend=ift==num_threads-1{data.len()}else{(t+1)*chunk_size};letsum:i32=data[start..end].iter().sum();// 写入部分和letmutsums=partial_sums.lock().unwrap();sums[t]=sum;drop(sums);// 等待所有线程完成部分计算letresult=barrier.wait();// 由领导者汇总ifresult.is_leader(){letsums=partial_sums.lock().unwrap();lettotal:i32=sums.iter().sum();println!("总和 = {}",total);}});handles.push(handle);}forhandleinhandles{handle.join().unwrap();}}

输出结果:

总和 = 5050

在这个示例中,Barrier确保了所有线程都完成部分和计算后,才由领导者线程统一汇总,避免了汇总过早执行导致的结果不完整问题。

7、注意事项与常见陷阱

7.1、 参与者数量必须匹配

Barrier::new(n)中的n必须与实际调用wait()的线程数量一致。如果某个线程提前退出或未调用wait(),其它线程将永远阻塞:

// 错误示例:创建了 3 个参与者的栅栏,但只有 2 个线程调用 wait()letbarrier=Arc::new(Barrier::new(3));// 只有 2 个线程调用 barrier.wait(),第 3 个永远不会到达// 结果:两个线程永久阻塞

7.2、 线程 panic 会导致死锁

如果某个线程在调用wait()之前 panic 退出,其它线程将永远等待:

letbarrier=Arc::new(Barrier::new(3));// 某个线程 panic 了thread::spawn(move||{panic!("出错了");// barrier.wait() 永远不会执行});

7.3、 不要在线程池中滥用

在线程池场景中,线程数量是动态的,使用Barrier需要格外小心,确保每次参与同步的线程数量与Barrier的参与者数量一致。

7.4、 与 Condvar 的区别

Barrier和Condvar都能实现线程等待,但语义不同:

  • Barrier:所有线程互相等待,直到全部到达。
  • Condvar:线程等待某个条件成立,由其它线程主动通知。

8、总结

std::sync::Barrier是 Rust 标准库中一个简洁而强大的同步原语,核心要点如下:

  • 会合机制:让一组线程在指定执行点相互等待,全部到达后一起放行。
  • 可复用:通过栅栏后自动重置,适合多轮分阶段任务。
  • 领导者识别:通过wait()返回的BarrierWaitResult::is_leader()识别最后一个到达的线程,用于执行汇总等一次性工作。
  • 适用场景:并行计算的分阶段同步、多线程任务的对齐、分布式计算的本地会合等。

掌握Barrier,你就能在多线程编程中更好地控制线程的执行节奏,写出更健壮的并发代码。

二、示例代码

usestd::sync::{Arc,Barrier};usestd::thread;usestd::time::Duration;fnmain(){constTHREAD_COUNT:usize=4;// 创建栅栏:等待4个线程全部到达才放行letbarrier=Arc::new(Barrier::new(THREAD_COUNT));letmuthandles=Vec::with_capacity(THREAD_COUNT);forthread_idin0..THREAD_COUNT{letb=Arc::clone(&barrier);leth=thread::spawn(move||{// ========== 第一轮栅栏同步 ==========println!("[线程{}] 执行前置任务,准备等待栅栏(第1轮)",thread_id);// 模拟每个线程耗时不等的前置工作thread::sleep(Duration::from_millis((thread_idasu64)*200));// 到达栅栏点,阻塞等待letres1=b.wait();// is_leader():仅有一个线程拿到trueifres1.is_leader(){println!("[线程{} === LEADER ===] 第一轮全部线程就绪,统一放行",thread_id);}else{println!("[线程{}] 栅栏放行,开始执行第一轮后续逻辑",thread_id);}// ========== 第二轮栅栏同步(Barrier可重复使用) ==========thread::sleep(Duration::from_millis(100));println!("[线程{}] 完成第一轮业务,等待第二轮栅栏",thread_id);letres2=b.wait();ifres2.is_leader(){println!("[线程{} === LEADER ===] 第二轮全部线程就绪,统一放行",thread_id);}else{println!("[线程{}] 第二轮栅栏放行",thread_id);}format!("线程{} 执行完成",thread_id)});handles.push(h);}// 等待所有子线程结束forhinhandles{letret=h.join().unwrap();println!("{}",ret);}println!("全部线程执行完毕");}
PSE:\leaning\slint\test>cargo runRunning`target\debug\ftp-client.exe`[线程0]执行前置任务,准备等待栅栏(第1轮)[线程1]执行前置任务,准备等待栅栏(第1轮)[线程2]执行前置任务,准备等待栅栏(第1轮)[线程3]执行前置任务,准备等待栅栏(第1轮)[线程3===LEADER===]第一轮全部线程就绪,统一放行[线程1]栅栏放行,开始执行第一轮后续逻辑[线程2]栅栏放行,开始执行第一轮后续逻辑[线程0]栅栏放行,开始执行第一轮后续逻辑[线程0]完成第一轮业务,等待第二轮栅栏[线程2]完成第一轮业务,等待第二轮栅栏[线程3]完成第一轮业务,等待第二轮栅栏[线程1]完成第一轮业务,等待第二轮栅栏[线程1===LEADER===]第二轮全部线程就绪,统一放行[线程3]第二轮栅栏放行[线程0]第二轮栅栏放行[线程2]第二轮栅栏放行 线程0执行完成 线程1执行完成 线程2执行完成 线程3执行完成 全部线程执行完毕PSE:\leaning\slint\test>

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 11:25:01

DV和PV到底在验证什么?汽车零部件设计与生产验证的核心区别

搞过汽车项目的人都知道&#xff0c;DV和PV几乎是天天挂在嘴边的词。但如果你随机拉住一个刚入行的工程师&#xff0c;问他“DV和PV到底在验什么”&#xff0c;多半会得到一个含糊的答案&#xff1a;做试验嘛&#xff0c;验证一下产品行不行。再追问一句“为什么有些试验DV做了…

作者头像 李华
网站建设 2026/9/27 11:17:52

嵌入式驱动开发核心解析:从裸机寄存器到Linux设备树

都说嵌入式是个坑&#xff0c;可每年还是有一堆人往里头跳。我这几年一直在跟嵌入式驱动打交道&#xff0c;面试过不少应届生&#xff0c;也带过几个新人&#xff0c;发现大家对"驱动开发到底忙啥"这件事&#xff0c;普遍没搞清楚。有的是被网上那些"驱动开发月…

作者头像 李华
网站建设 2026/9/27 11:08:36

汇写论文AI智能写作,四步生成全篇原创

又到一年毕业季&#xff0c;"论文"两个字成了无数专科、本科、硕士乃至博士学子心头挥之不去的阴影。选题没有方向、文献查不齐全、框架无从下手、写出来的重复率居高不下、AIGC率一查就爆表……只要一环卡住&#xff0c;整篇稿子就步步被动&#xff0c;多少个深夜只…

作者头像 李华