Skip to content

Commit 2c5b055

Browse files
committed
added a parker-bonus library
1 parent 0ce7fb6 commit 2c5b055

File tree

3 files changed

+142
-0
lines changed

3 files changed

+142
-0
lines changed

ch11/parker-bonus/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "parker-bonus"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]

ch11/parker-bonus/README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Parker
2+
3+
As we explained in the book, relying on `thread::park` alone
4+
is not a good solution since "everyone" can use `thread::current`
5+
and `thread::park` to implement simple synchronization in their code.
6+
7+
By doing so they can cause us to miss wakeups or to simply deadlock
8+
since we rely on the same mechanism for parking our executor.
9+
10+
Since it doesn't require many lines of code to create a working solution ourselves we'll show how
11+
we can solve that by using a `Condvar` and a `Mutex` instead, but there are also libraries
12+
that does this for you. One of the popular ones is the [Parker](https://docs.rs/crossbeam/latest/crossbeam/sync/struct.Parker.html) provided by the crossbeam crate.
13+
14+
If you want to write one yourself, it can be as simple as this:
15+
16+
```rust, ignore
17+
#[derive(Default)]
18+
struct Parker(Mutex<bool>, Condvar);
19+
20+
impl Parker {
21+
fn park(&self) {
22+
23+
// We aquire a lock to the Mutex which protects our flag indicating if we
24+
// should resume execution or not.
25+
let mut resumable = self.0.lock().unwrap();
26+
27+
// We put this in a loop since there is a chance we'll get woken, but
28+
// our flag hasn't changed. If that happens, we simply go back to sleep.
29+
while !*resumable {
30+
31+
// We sleep until someone notifies us
32+
resumable = self.1.wait(resumable).unwrap();
33+
}
34+
35+
// We immidiately set the condition to false, so that next time we call `park` we'll
36+
// go right to sleep.
37+
*resumable = false;
38+
}
39+
40+
fn unpark(&self) {
41+
// We simply acquire a lock to our flag and sets the condition to `runnable` when we
42+
// get it.
43+
*self.0.lock().unwrap() = true;
44+
45+
// We notify our `Condvar` so it wakes up and resumes.
46+
self.1.notify_one();
47+
}
48+
}
49+
```
50+
51+
The `Condvar` in Rust is designed to work together with a Mutex. Usually, you'd think that we don't
52+
release the mutex-lock we acquire in `self.0.lock().unwrap();` before we go to sleep. Which means
53+
that our `unpark` function never will acquire a lock to our flag and we deadlock.
54+
55+
Using `Condvar` we avoid this since the `Condvar` will consume our lock so it's released at the
56+
moment we go to sleep.
57+
58+
When we resume again, our `Condvar` returns our lock so we can continue to operate on it.
59+
60+
## Usage
61+
62+
```rust
63+
use std::{thread, time, sync::{Arc, atomic::{AtomicBool, Ordering}}};
64+
65+
let flag = Arc::new(AtomicBool::new(false));
66+
let parker = Arc::new(Parker::default());
67+
68+
let flag_clone = flag.clone();
69+
let parker_clone = parker.clone();
70+
71+
thread::spawn(move || {
72+
thread::sleep(time::Duration::from_millis(200));
73+
flag_clone.store(true, Ordering::SeqCst);
74+
parker_clone.unpark();
75+
});
76+
assert!(!flag.load(Ordering::SeqCst), "Flag should be false at this point!");
77+
parker.park();
78+
assert!(flag.load(Ordering::SeqCst), "Flag should be true at this point!");
79+
```

ch11/parker-bonus/src/lib.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use std::sync::{Mutex, Condvar};
2+
3+
#[derive(Default)]
4+
pub struct Parker(Mutex<bool>, Condvar);
5+
6+
impl Parker {
7+
pub fn park(&self) {
8+
9+
// We aquire a lock to the Mutex which protects our flag indicating if we
10+
// should resume execution or not.
11+
let mut resumable = self.0.lock().unwrap();
12+
13+
// We put this in a loop since there is a chance we'll get woken, but
14+
// our flag hasn't changed. If that happens, we simply go back to sleep.
15+
while !*resumable {
16+
17+
// We sleep until someone notifies us
18+
resumable = self.1.wait(resumable).unwrap();
19+
}
20+
21+
// We immidiately set the condition to false, so that next time we call `park` we'll
22+
// go right to sleep.
23+
*resumable = false;
24+
}
25+
26+
pub fn unpark(&self) {
27+
// We simply acquire a lock to our flag and sets the condition to `runnable` when we
28+
// get it.
29+
*self.0.lock().unwrap() = true;
30+
31+
// We notify our `Condvar` so it wakes up and resumes.
32+
self.1.notify_one();
33+
}
34+
}
35+
36+
37+
#[test]
38+
fn parker_works() {
39+
use std::{thread, time, sync::{Arc, atomic::{AtomicBool, Ordering}}};
40+
41+
let flag = Arc::new(AtomicBool::new(false));
42+
let parker = Arc::new(Parker::default());
43+
44+
let flag_clone = flag.clone();
45+
let parker_clone = parker.clone();
46+
47+
thread::spawn(move || {
48+
thread::sleep(time::Duration::from_millis(200));
49+
flag_clone.store(true, Ordering::SeqCst);
50+
parker_clone.unpark();
51+
});
52+
assert!(!flag.load(Ordering::SeqCst), "Flag should be false at this point!");
53+
parker.park();
54+
assert!(flag.load(Ordering::SeqCst), "Flag should be true at this point!");
55+
}

0 commit comments

Comments
 (0)