【问题标题】:Why does a Condvar not wake the last thread?为什么 Condvar 不唤醒最后一个线程?
【发布时间】:2019-04-01 21:54:43
【问题描述】:

在玩Condvar 时,我得到了意想不到的结果。我知道我不能相信我的wait() 不会早起,但在我的情况下,我的一个唤醒似乎一直缺失。鉴于此示例代码:

use std::sync::{Arc, Mutex, Condvar};
use std::{
    thread,
    time
};

fn main() {
    let pair = Arc::new((Mutex::new(false), Condvar::new()));
    let pause = time::Duration::from_secs(1);

    for id in 0..10 {
        let pair2 = pair.clone();
        thread::spawn(move|| {
            let &(ref lock, ref cvar) = &*pair2;

            let lock = lock.lock().unwrap();
            let _ = cvar.wait(lock).unwrap();
            println!("Thread {} done!", id);
        });
    }

    // Wait for the thread to start up.
    let &(ref _lock, ref cvar) = &*pair;

    for _ in 0..10 {
        thread::sleep(pause);
        cvar.notify_one();
    }
}

我只让前八个线程脱离循环:

Thread 0 done!
Thread 1 done!
Thread 2 done!
Thread 3 done!
Thread 4 done!
Thread 5 done!
Thread 6 done!
Thread 7 done!
Thread 8 done!

如果我将第二个循环的计数增加到 11,它实际上会唤醒所有 9 个。

我已经在wait()notify_one() 上仔细检查了the documentation,但这里有什么问题并不明显。

有什么想法吗?这是一个错误还是我做错了什么?

【问题讨论】:

    标签: multithreading rust


    【解决方案1】:

    您的主线程不会等待工作线程完成,并且根据the documentation of std::thread::spawn

    […] 子线程可能比父线程更长寿(除非父线程是主线程;当主线程完成时整个进程终止)。

    (重点是我的)

    因为您的程序在最后一个 notify_one 之后立即终止,所以工作线程可能会在打印值之前被终止。

    当你循环 11 次时,在第 10 个notify_all 之后有 1 秒的睡眠,这意味着最新的工作线程可能会完成它的工作。

    该问题的常见解决方案是收集spawn 返回的JoinHandles,并按照sn99's answer 的方式等待它们。

    【讨论】:

    • 啊!当然。那是我的愚蠢...谢谢大家。选择这个是因为它引用了文档并为其他人深入解释。
    【解决方案2】:

    将代码改为:

    use std::sync::{Arc, Condvar, Mutex};
    use std::{thread, time};
    
    fn main() {
        let pair = Arc::new((Mutex::new(false), Condvar::new()));
        let pause = time::Duration::from_secs(1);
    
        // Create a vector of `JoinHandle` 
        let mut thread_handles = Vec::new();
    
        for id in 0..10 {
            let pair2 = pair.clone();
            // Push JoinHandle in the vector
            thread_handles.push(thread::spawn(move || {
                let &(ref lock, ref cvar) = &*pair2;
    
                let lock = lock.lock().unwrap();
                let _ = cvar.wait(lock).unwrap();
                println!("Thread {} done!", id);
            }));
        }
    
        // Wait for the thread to start up.
        let &(ref _lock, ref cvar) = &*pair;
    
        for _ in 0..10 {
            thread::sleep(pause);
            cvar.notify_one();
        }
    
        // Wait for all threads to complete before exiting `main`
        // handles is of type JoinHandle
        for handles in thread_handles {
            handles.join().unwrap();
        }
    }
    

    join 方法等待所有线程完成。这是必要的,因为即使其他线程仍在运行,Rust 程序也会在 main 返回时立即退出。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-10
      • 2020-11-09
      • 2022-11-03
      相关资源
      最近更新 更多