信号量的类型定义
一般来说,信号量(semaphore)(S),表示资源数量减去需求数量。信号量的值仅能由 PV 操作来改变。
执行一次 P 操作意味着请求一个单位资源,因此 (S) 的值减 1;当 (S < 0) 时,需求数大于资源数,即已经没有可用资源,请求者必须等待。
执行一个 V 操作意味着释放一个单位资源,因此 (S) 的值加 1;若 (S ge 0),此时资源数目能满足需求,所以可以唤醒一个在等待的请求者,让它获取资源。
当 (S < 0),(|S|) 即没有得到资源的请求者数目,也就是阻塞的线程数。
用互斥量和条件变量可以模拟信号量的行为:
use std::sync::Condvar;
use std::sync::Mutex;
pub struct Semaphore {
sem: Mutex<i32>,
con: Condvar,
}
impl Semaphore {
pub fn new(sem: i32) -> Self {
Self {
sem: Mutex::new(sem),
con: Condvar::new(),
}
}
pub fn p(&self) {
let mut sem = self.sem.lock().unwrap();
*sem -= 1;
if *sem < 0 {
let _ = self.con.wait(sem).unwrap();
}
}
pub fn v(&self) {
let mut sem = self.sem.lock().unwrap();
*sem += 1;
if *sem <= 0 {
self.con.notify_one();
}
}
}
互斥
当信号量的初始值取 1,任何第二个请求资源者会陷入饥饿。从而同一时刻只能有一个线程访问某个资源。
初始化一个信号量 mutex
为 Semaphore::new(1)
,在使用临界资源之前调用 mutex.p()
,使用完资源之后调用 mutex.v()
。即实现了资源的互斥访问。
同步
设置起始资源为大于 1 的正数,则 PV 操作维护资源同步。
举例说明,要实现有 3 个缓冲区的、MPMS 的队列。抽象出两种资源:缓冲区空间(room)和队列中的数据(data)。队列为空的情况下,有 3 个 room 资源,没有 data 资源。
let room = Semaphore::new(3);
let data = Semaphore::new(0);
let queue = Queue::empty();
要往队列里添加元素,则需要一个 room 资源,操作完之后产生一个 data 资源。
room.p();
queue.push(x);
data.v();
要从队列头取出元素,则需要一个 data 资源,操作完之后产生一个 room 资源。
data.p();
let x = queue.pop();
room.v();
(完)