0

I'm having unexpected results when playing around with Condvar. I know that I can't trust that my wait() won't wake up early, but in my case it seems that consistently one of my wake-ups is missing. Given this example code:

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();
    }
}

I'm only getting the first eight threads out of the loop:

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!

If I increase the count on the second loop to 11, it does actually wake up all nine.

I've double checked the documentation on both wait() and notify_one() but it's not obvious what's wrong here.

Any thoughts? Is this a bug or something I'm just not doing right?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
RandomInsano
  • 1,204
  • 2
  • 16
  • 36

2 Answers2

4

Your main thread does not wait for the worker threads to finish, and according to the documentation of std::thread::spawn:

[…] the child thread may outlive the parent (unless the parent thread is the main thread; the whole process is terminated when the main thread finishes).

(emphasis is mine)

Because your program terminates immediately after the last notify_one, the worker thread will likely be killed before printing the value.

When you loop 11 times, there is a sleep of 1s after the 10th notify_all, which means the latest worker thread will likely finish its job.

A common solution to the problem is to collect the JoinHandles returned by spawn, and wait for them, as per sn99's answer.

mcarton
  • 27,633
  • 5
  • 85
  • 95
  • Ah! Of course. That was dumb of me... Thanks guys. Picked this one as it references docs and explains in-depth for others. – RandomInsano Apr 01 '19 at 23:32
1

Change the code to :

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();
    }
}

The join method waits for all threads to finish. This is necessary because a Rust program exits as soon as main returns, even if other threads are still running.

sn99
  • 843
  • 8
  • 24