信号量(Semaphore)是一种对资源并发访问控制的方式。
区别于互斥锁(Mutex)是对共享资源的独占访问,Semaphore允许指定多个并发访问共享资源。
就是说Semaphore像一个持有令牌(permit/token)的桶,每一个并发访问需要持有(acquire)一个令牌来访问共享资源,
当没有令牌时,没法访问共享资源,直到有新的令牌加入(add)或者原来发出的令牌放回(release)桶中。
接下来,我们尝试用通过用它来实现两个线程交替打印1和2,来更直观了解如何使用semaphore
Rust std库中没有正式发布的semaphore(std::sync::Semaphore在1.7.0废弃了)。下边用tokio库提供的semaphore
首先安装 tokio库
1 2 3 4
| # 手动添加tokio到cargo.toml # 或使用cargo-add: cargo add tokio --features sync,macros,rt-multi-thread [dependencies] tokio = { version = "1.34.0", features = ["sync", "macros", "rt-multi-thread"] }
|
先来一版常规实现,初始化一个只有一个令牌的semahore,两个线程去并发持有令牌,用后释放(通过drop)令牌,实现交替打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| use std::sync::Arc; use tokio::sync::Semaphore;
#[tokio::main] async fn main() { let semaphore = Arc::new(Semaphore::new(1)); let cnt = 3; let semaphore2 = semaphore.clone();
let t1 = tokio::spawn(async move { for _ in 0..cnt { let permit = semaphore.acquire().await.unwrap(); print!("1 "); drop(permit); } });
let t2 = tokio::spawn(async move { for _ in 0..cnt { let _ = semaphore2.acquire().await.unwrap(); print!("2 "); } });
tokio::try_join!(t1, t2).unwrap(); }
|
乍看没什么问题,但是打印其实不一定是1 2 1 2 1 2
的顺序。
原因很简单,我们只是约束了令牌同时只能有一个线程获取到,但是没有约束谁先谁后啊。所以其实没有实现交替打印。
怎么交替打印呢?
要控制顺序,我们可以让每个线程所持有的semaphore里的令牌时动态增加和消耗,然后一个令牌桶数量的增加滞后于另一个。
增加可以用add_permits, 消耗后不放回可以用forgot, 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| use std::sync::Arc; use tokio::sync::Semaphore;
#[tokio::main] async fn main() { let semaphore = Arc::new(Semaphore::new(1)); let cnt = 3; let semaphore2 = semaphore.clone();
let semaphore_wait = Arc::new(Semaphore::new(0)); let semaphore_wait2 = semaphore_wait.clone();
let t1 = tokio::spawn(async move { for _ in 0..cnt { let permit = semaphore.acquire().await.unwrap(); print!("1 "); permit.forget(); semaphore_wait2.add_permits(1); } });
let t2 = tokio::spawn(async move { for _ in 0..cnt { let permit = semaphore_wait.acquire().await.unwrap(); print!("2 "); permit.forget(); semaphore2.add_permits(1); } });
tokio::try_join!(t1, t2).unwrap(); }
|
通过两个动态的令牌桶(semaphore)线程的执行顺序就能交替执行了。
可以和上篇 condvar实现的版本 对比下, 感受下semaphore的魅力。
如有疑问,请文末留言交流或邮件:newbvirgil@gmail.com
本文链接 : https://newbmiao.github.io/2023/11/24/rust-sync-semaphore.html