[Day14] Read Rust Atomics and Locks - Thread Parking and condition variables
by Mara Bos
From: Waiting: Parking and Condition Variables
To: Condition Variables
At Topics: Chapter 1. Basics of Rust Concurrency
Recall
std::thread::scope
: It allows us to spawn threads that cannot outlive the scope of the closure we pass to that function, making it possible to safely borrow local variables.MutexGuard
: it represents the guarantee that we have locked the mutex.
Notes
When data is mutated by multiple threads, there are many situations where they would need to wait for some event, for some condition about the data to become true.
For example, if we have a mutex protecting a Vec, we might want to wait until it contains anything.
At this point, we need thread parking or condition variables
Thread Parking
std::thread::park()
- A thread can park itself, which puts it to sleep
- Another thread can unpark the parked thread, waking it up from its nap
- Unpark requests don’t stack up: Calling unpark two times only has one time's effect
- The request to unpark is recorded, even if it is called before the thread parks itself
Use
std::thread::current()
orJoinHandle
returned byspawn
of the the parked thread and callunpark()
Warning:
park()
does not guarantee that it will only return (wake up) because of a matchingunpark()
(spurious wake-ups, 虛假的喚醒)
Here is an simple example:
use std::sync::Mutex; use std::collections::VecDeque; use std::thread; use std::time::Duration; fn main() { let que = Mutex::new(VecDeque::new()); thread::scope(|s| { // consuming thread let t = s.spawn(|| loop { let item = que.lock().unwrap().pop_front(); if let Some(item) = item { dbg!(item); } else { // it’s important that we only park the thread if we’ve seen the queue is empty thread::park(); } }); // Producing thread for i in 0.. { que.lock().unwrap().push_back(i); t.thread().unpark(); thread::sleep(Duration::from_secs(1)); } }); }
Condition Variables
- Two basic operations:
wait
andnotify
std::sync::Condvar
Condvar
only works when used together with a Mutex- Besides, normally, a
Condvar
is only ever used together with a single Mutex.
Eg:
// source: https://github.com/m-ou-se/rust-atomics-and-locks/blob/main/examples/ch1-12-condvar.rs use std::collections::VecDeque; use std::sync::Condvar; use std::sync::Mutex; use std::thread; use std::time::Duration; fn main() { let queue = Mutex::new(VecDeque::new()); let not_empty = Condvar::new(); // it contains two threads thread::scope(|s| { s.spawn(|| { loop { let mut q = queue.lock().unwrap(); let item = loop { if let Some(item) = q.pop_front() { break item; } else { q = not_empty.wait(q).unwrap(); } }; drop(q); dbg!(item); } }); for i in 0.. { queue.lock().unwrap().push_back(i); not_empty.notify_one(); thread::sleep(Duration::from_secs(1)); } }); }