ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Rust std::sync::Condvar 条件变量详解

Rust std::sync::Condvar 条件变量详解 Rust std::sync::Condvar 条件变量详解一、条件变量详解1、引言2、 什么是条件变量3、 Condvar 的核心 API3.1 、关键设计wait 与锁的配合4、实战生产者-消费者模型4.1、 为什么用 while 而不是 if5、带超时的等待6、 常见陷阱与最佳实践6.1、 必须与 Mutex 配合使用6.2、 防止丢失唤醒6.3、 优先使用 notify_one6.4 、避免持锁调用 notify7、 总结二、代码示例一、条件变量详解1、引言在多线程编程中线程之间不仅需要共享数据还需要在特定条件满足时进行同步与通信。Rust 标准库提供了std::sync::Condvar条件变量用于实现线程间的等待与唤醒机制。本文将深入剖析 Condvar 的原理、用法与常见陷阱帮助你写出安全高效的并发代码。2、 什么是条件变量条件变量Condition Variable是一种同步原语它允许一个或多个线程在某个条件不满足时进入阻塞等待状态并在条件可能满足时被其他线程唤醒。条件变量通常与互斥锁Mutex配合使用核心价值在于避免忙等待线程无需反复轮询检查条件节省 CPU 资源。精确唤醒只有条件真正变化时才唤醒等待线程减少无效竞争。解耦等待与通知生产者与消费者无需直接感知对方的存在。3、 Condvar 的核心 APIRust 的Condvar定义在std::sync模块中常用方法如下方法说明new()创建新的条件变量wait(guard)阻塞当前线程等待通知自动释放锁并重新获取wait_timeout(guard, duration)带超时等待返回是否超时notify_one()唤醒一个等待线程notify_all()唤醒所有等待线程3.1 、关键设计wait 与锁的配合wait方法接收一个MutexGuard其内部执行三个原子步骤释放互斥锁阻塞当前线程被唤醒后重新获取互斥锁。这三个步骤在底层是不可分割的从而避免了「条件刚检查完、锁还没释放就被通知」的竞态问题。4、实战生产者-消费者模型下面通过一个经典的生产者-消费者示例演示 Condvar 的基本用法。usestd::sync::{Arc,Condvar,Mutex};usestd::thread;usestd::time::Duration;fnmain(){// 共享队列数据 条件变量letpairArc::new((Mutex::new(Vec::new()),Condvar::new()));letpair_cloneArc::clone(pair);// 生产者线程letproducerthread::spawn(move||{let(lock,cvar)*pair_clone;foriin0..5{letmutqueuelock.lock().unwrap();queue.push(i);println!(生产者: 放入 {},i);// 通知消费者cvar.notify_one();drop(queue);thread::sleep(Duration::from_millis(500));}});// 消费者线程letconsumerthread::spawn(move||{let(lock,cvar)*pair;loop{letmutqueuelock.lock().unwrap();// 条件不满足时等待whilequeue.is_empty(){queuecvar.wait(queue).unwrap();}letvaluequeue.remove(0);println!(消费者: 取出 {},value);ifvalue4{break;}}});producer.join().unwrap();consumer.join().unwrap();println!(任务完成);}4.1、 为什么用 while 而不是 if在等待条件时必须使用while循环反复检查条件原因有二虚假唤醒操作系统可能在没有notify的情况下唤醒线程多消费者竞争多个消费者被唤醒后其中一个可能已经消费掉数据其余线程需要重新检查。5、带超时的等待在某些场景下我们不希望线程无限期阻塞。wait_timeout可以设置最长等待时间usestd::sync::{Arc,Condvar,Mutex};usestd::thread;usestd::time::Duration;fnmain(){letpairArc::new((Mutex::new(false),Condvar::new()));letpair_cloneArc::clone(pair);// 等待线程letwaiterthread::spawn(move||{let(lock,cvar)*pair_clone;letmutreadylock.lock().unwrap();let(guard,timeout)cvar.wait_timeout(ready,Duration::from_secs(2)).unwrap();readyguard;iftimeout.timed_out(){println!(等待超时条件仍未满足);}else{println!(条件已满足: {},*ready);}});thread::sleep(Duration::from_secs(1));// 通知线程let(lock,cvar)*pair;letmutreadylock.lock().unwrap();*readytrue;cvar.notify_one();drop(ready);waiter.join().unwrap();}6、 常见陷阱与最佳实践6.1、 必须与 Mutex 配合使用Condvar 本身不保护共享数据必须搭配Mutex使用。wait需要传入MutexGuard这正是为了在等待期间安全地释放锁。6.2、 防止丢失唤醒在检查条件与调用wait之间不能有其他线程插入修改条件。Rust 的wait将「释放锁 阻塞」合并为原子操作从语言层面规避了这个问题。6.3、 优先使用 notify_one当只有一个线程需要被唤醒时使用notify_one比notify_all更高效可以减少线程竞争与上下文切换。6.4 、避免持锁调用 notify虽然 Rust 允许在持锁状态下调用notify但建议先释放锁再通知这样可以避免唤醒的线程立即因抢锁失败而再次阻塞提升性能。7、 总结Condvar 是 Rust 并发编程中实现线程间条件同步的重要工具。掌握其核心 API 与「while 循环检查条件」的规范写法能够帮助你构建健壮的多线程应用。在实际开发中也可以考虑使用std::sync::mpsc通道或第三方库如crossbeam来简化某些场景的通信逻辑。二、代码示例usestd::sync::{Arc,Condvar,Mutex};usestd::thread;usestd::time::Duration;fndemo_basic_notify_one(){println!( demo_basic_notify_onenotify_one 唤醒单个线程 );letpairArc::new((Mutex::new(false),Condvar::new()));letpair_cloneArc::clone(pair);lethandlethread::spawn(move||{let(lock,cvar)*pair_clone;letmutguardlock.lock().unwrap();while!*guard{println!(子线程条件不满足进入 wait 等待);guardcvar.wait(guard).unwrap();}println!(子线程收到唤醒信号条件为 true继续执行);});thread::sleep(Duration::from_millis(800));let(lock,cvar)*pair;{letmutguardlock.lock().unwrap();*guardtrue;println!(主线程修改条件执行 notify_one());cvar.notify_one();// 离开这个大括号guard 自动 drop释放锁}handle.join().unwrap();println!();}fndemo_notify_all(){println!( demo_notify_allnotify_all 广播唤醒全部等待线程 );letdataArc::new((Mutex::new(false),Condvar::new()));letmuthandlesVec::new();foriin0..3{letdata_cloneArc::clone(data);leththread::spawn(move||{let(lock,cvar)*data_clone;letmutguardlock.lock().unwrap();while!*guard{println!(线程{}等待唤醒,i);guardcvar.wait(guard).unwrap();}println!(线程{}被 notify_all 唤醒,i);});handles.push(h);}thread::sleep(Duration::from_millis(600));let(lock,cvar)*data;{letmutglock.lock().unwrap();*gtrue;println!(主线程设置条件 truenotify_all());cvar.notify_all();}forhinhandles{h.join().unwrap();}println!();}fndemo_wait_timeout(){println!( demo_wait_timeout带超时等待两种结果唤醒 / 超时 );letpairArc::new((Mutex::new(false),Condvar::new()));letpair_cloneArc::clone(pair);lethandlethread::spawn(move||{let(lock,cvar)*pair_clone;letmutguardlock.lock().unwrap();while!*guard{letrescvar.wait_timeout(guard,Duration::from_secs(1)).unwrap();guardres.0;ifres.1.timed_out(){println!(子线程wait_timeout 超时返回);break;}else{println!(子线程被信号唤醒);}}});handle.join().unwrap();println!();}fndemo_producer_consumer(){println!( demo_producer_consumer 生产者消费者 );letinnerArc::new((Mutex::new(Vec::new()),Condvar::new()));letmuthandlesVec::new();letc_innerArc::clone(inner);handles.push(thread::spawn(move||{let(buf_lock,cvar)*c_inner;letmutguardbuf_lock.lock().unwrap();loop{whileguard.is_empty(){println!(消费者队列为空等待生产);guardcvar.wait(guard).unwrap();}letvalguard.remove(0);println!(消费者取出 {},val);ifval99{println!(消费者收到结束标记退出);break;}}}));letp_innerArc::clone(inner);handles.push(thread::spawn(move||{let(buf_lock,cvar)*p_inner;fornumin[10,20,30,99]{letmutgbuf_lock.lock().unwrap();g.push(num);println!(生产者放入 {},num);cvar.notify_one();drop(g);thread::sleep(Duration::from_millis(300));}}));forhinhandles{h.join().unwrap();}println!();}fnmain(){demo_basic_notify_one();demo_notify_all();demo_wait_timeout();demo_producer_consumer();println!(全部示例运行完成);}demo_basic_notify_onenotify_one 唤醒单个线程子线程条件不满足进入 wait 等待 主线程修改条件执行notify_one()子线程收到唤醒信号条件为true继续执行demo_notify_allnotify_all 广播唤醒全部等待线程线程0等待唤醒 线程1等待唤醒 线程2等待唤醒 主线程设置条件truenotify_all()线程1被 notify_all 唤醒 线程2被 notify_all 唤醒 线程0被 notify_all 唤醒demo_wait_timeout带超时等待两种结果唤醒/超时子线程wait_timeout 超时返回demo_producer_consumer 生产者消费者消费者队列为空等待生产 生产者放入10消费者取出10消费者队列为空等待生产 生产者放入20消费者取出20消费者队列为空等待生产 生产者放入30消费者取出30消费者队列为空等待生产 生产者放入99消费者取出99消费者收到结束标记退出 全部示例运行完成PSE:\leaning\slint\test
返回列表