Rust 学习笔记(34)-Condvar

参考章节《Rust语言圣经(Rust Course)》第3.6.4章 用条件变量(Condvar)控制线程的同步

如何控制线程执行的顺序?

可以保证线程安全,但有时我们想控制线程执行的顺序该怎么办呢?

答: Rust 为我们提供了而 Condvar(Condition Variables) 条件变量
它通常和Mutex<T>一起使用,一般用于生产者消费者模型,它可以让一个线程进入等待(锁),直至被其他线程唤醒

话不多说,我们来看看下面的代码

我们有一个生产者线程(铁匠),他生产(打造)一件装备后,通知消费者线程(玩家)消费(购买)
消费者线程(玩家)消费(购买)后,通知生产者线程(铁匠)继续生产(打造),直到生产者线程退出

 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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
use std::{
    sync::{Arc, Condvar, Mutex},
    thread,
};

fn main() {
    // 条件变量,它可以让一个线程进入等待(锁),直至被其他线程唤醒
    let cond = Arc::new(Condvar::new());
    let cond_clone = cond.clone();

    // 互斥锁和条件变量组合使用时,一般用于条件判断依据
    let mutex_lock = Arc::new(Mutex::new(false));
    let mutex_lock_clone = mutex_lock.clone();

    let mut total_count = 10;

    thread::spawn(move || loop {
        // 这里利用作用域获取了锁(MutexGuard<T>)的值后直接让它释放
        let mut lock = { *mutex_lock_clone.lock().unwrap() };

        // 如果 lock == false 表示铁匠(生产者)还没有打造出装备,因此进入等待,等待铁匠(生产者)打造装备
        while lock == false {
            // 这里进入等待前一定要释放 lock,否则你拿着锁进入等待,其他线程就无法获得锁了
            // 这就是为什么上面要用作用域释放 lock 的原因
            lock = *cond_clone.wait(mutex_lock_clone.lock().unwrap()).unwrap(); // 进入等待
        }

        // 如果 lock == true 表示铁匠(生产者)已经打造出了一件装备,因此我们就可以购买(消费)了
        println!("玩家(消费者),购买了一件装备");

        // 这里用用作用域包起来的原因,也是为了释放 lock
        // 因为如果你拿着锁去唤醒铁匠(生产者)线程,它也肯定无法获得锁
        {
            // 消费完后,把 lock 的值改成 false
            *mutex_lock_clone.lock().unwrap() = false;
        }

        // 唤醒生产者线程(其实这里是唤醒一个等待中的线程,只不过此时只有一个线程在等待,就是我们的生产者线程)
        cond_clone.notify_one();
    });

    while total_count > 0 {
        // 这里利用作用域获取了锁(MutexGuard<T>)的值后直接让它释放
        let mut lock = { *mutex_lock.lock().unwrap() };

        // 如果 lock == true 表示已经打造了一件装备,因此暂停生产,等待玩家(消费者)购买(消费)
        while lock == true {
            // 这里进入等待前一定要释放 lock,否则你拿着锁进入等待,其他线程就无法获得锁了
            // 这就是为什么上面要用作用域释放 lock 的原因
            lock = *cond.wait(mutex_lock.lock().unwrap()).unwrap(); // 进入等待
        }

        // 否则如果 lock == false 表示还没有打造装备,因此需要打造一件装备
        total_count -= 1;
        println!(
            "铁匠(生产者),打造了一件装备,剩余材料总数: {}",
            total_count
        );

        // 这里用用作用域包起来的原因,也是为了释放 lock
        // 因为如果你拿着锁去唤醒玩家(消费者)线程,它也肯定无法获得锁
        {
            // 打造完后,把 lock 的值改成 true
            *mutex_lock.lock().unwrap() = true;
        }

        // 唤醒消费者线程(其实这里是唤醒一个等待中的线程,只不过此时只有一个线程在等待,就是我们的消费者线程)
        cond.notify_one();
    }
}

运行这段代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
铁匠(生产者),打造了一件装备,剩余材料总数: 9
玩家(消费者),购买了一件装备
铁匠(生产者),打造了一件装备,剩余材料总数: 8
玩家(消费者),购买了一件装备
铁匠(生产者),打造了一件装备,剩余材料总数: 7
玩家(消费者),购买了一件装备
铁匠(生产者),打造了一件装备,剩余材料总数: 6
玩家(消费者),购买了一件装备
铁匠(生产者),打造了一件装备,剩余材料总数: 5
玩家(消费者),购买了一件装备
铁匠(生产者),打造了一件装备,剩余材料总数: 4
玩家(消费者),购买了一件装备
铁匠(生产者),打造了一件装备,剩余材料总数: 3
玩家(消费者),购买了一件装备
铁匠(生产者),打造了一件装备,剩余材料总数: 2
玩家(消费者),购买了一件装备
铁匠(生产者),打造了一件装备,剩余材料总数: 1
玩家(消费者),购买了一件装备
铁匠(生产者),打造了一件装备,剩余材料总数: 0
玩家(消费者),购买了一件装备

总结一下

  1. Condvar 可以让一个线程进入等待,直至被另一个线程唤醒
  2. 进入等待前,一定要释放持有的,否则其他线程无法获取
updatedupdated2025-03-012025-03-01