1
0
mirror of https://github.com/google/comprehensive-rust.git synced 2025-05-16 07:36:05 +02:00

Use chopsticks and limit philosophers. (#2655)

Use chopstick to explain why 2 are needed to eat.
Limit async to 2 philosophers so they can deadlock in tokio.
(Tested with [3, 4, 5] philosophers and they all were able to run
without deadlock
with lock ordering disabled.)

---------

Co-authored-by: Sterling Stein <scubed2+git@gmail.com>
This commit is contained in:
scubed2 2025-02-24 08:13:16 -08:00 committed by GitHub
parent f531d4dfd7
commit 32a8b4bf13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 50 additions and 44 deletions

View File

@ -16,8 +16,8 @@ code below to a file called `src/main.rs`, fill out the blanks, and test that
```rust,compile_fail ```rust,compile_fail
{{#include dining-philosophers.rs:Philosopher}} {{#include dining-philosophers.rs:Philosopher}}
// left_fork: ... // left_chopstick: ...
// right_fork: ... // right_chopstick: ...
// thoughts: ... // thoughts: ...
} }
@ -26,7 +26,7 @@ code below to a file called `src/main.rs`, fill out the blanks, and test that
{{#include dining-philosophers.rs:Philosopher-eat}} {{#include dining-philosophers.rs:Philosopher-eat}}
{{#include dining-philosophers.rs:Philosopher-eat-body}} {{#include dining-philosophers.rs:Philosopher-eat-body}}
{{#include dining-philosophers.rs:Philosopher-eat-end}} {{#include dining-philosophers.rs:Philosopher-eat-end}}
// Create forks // Create chopsticks
// Create philosophers // Create philosophers

View File

@ -18,13 +18,13 @@ use std::sync::Arc;
use tokio::sync::{mpsc, Mutex}; use tokio::sync::{mpsc, Mutex};
use tokio::time; use tokio::time;
struct Fork; struct Chopstick;
struct Philosopher { struct Philosopher {
name: String, name: String,
// ANCHOR_END: Philosopher // ANCHOR_END: Philosopher
left_fork: Arc<Mutex<Fork>>, left_chopstick: Arc<Mutex<Chopstick>>,
right_fork: Arc<Mutex<Fork>>, right_chopstick: Arc<Mutex<Chopstick>>,
thoughts: mpsc::Sender<String>, thoughts: mpsc::Sender<String>,
} }
@ -40,11 +40,11 @@ impl Philosopher {
// ANCHOR: Philosopher-eat // ANCHOR: Philosopher-eat
async fn eat(&self) { async fn eat(&self) {
// Keep trying until we have both forks // Keep trying until we have both chopsticks
// ANCHOR_END: Philosopher-eat // ANCHOR_END: Philosopher-eat
// Pick up forks... // Pick up chopsticks...
let _left_fork = self.left_fork.lock().await; let _left_chopstick = self.left_chopstick.lock().await;
let _right_fork = self.right_fork.lock().await; let _right_chopstick = self.right_chopstick.lock().await;
// ANCHOR: Philosopher-eat-body // ANCHOR: Philosopher-eat-body
println!("{} is eating...", &self.name); println!("{} is eating...", &self.name);
@ -56,30 +56,33 @@ impl Philosopher {
} }
} }
static PHILOSOPHERS: &[&str] = // tokio scheduler doesn't deadlock with 5 philosophers, so have 2.
&["Socrates", "Hypatia", "Plato", "Aristotle", "Pythagoras"]; static PHILOSOPHERS: &[&str] = &["Socrates", "Hypatia"];
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
// ANCHOR_END: Philosopher-eat-end // ANCHOR_END: Philosopher-eat-end
// Create forks // Create chopsticks
let mut forks = vec![]; let mut chopsticks = vec![];
(0..PHILOSOPHERS.len()).for_each(|_| forks.push(Arc::new(Mutex::new(Fork)))); PHILOSOPHERS
.iter()
.for_each(|_| chopsticks.push(Arc::new(Mutex::new(Chopstick))));
// Create philosophers // Create philosophers
let (philosophers, mut rx) = { let (philosophers, mut rx) = {
let mut philosophers = vec![]; let mut philosophers = vec![];
let (tx, rx) = mpsc::channel(10); let (tx, rx) = mpsc::channel(10);
for (i, name) in PHILOSOPHERS.iter().enumerate() { for (i, name) in PHILOSOPHERS.iter().enumerate() {
let mut left_fork = Arc::clone(&forks[i]); let mut left_chopstick = Arc::clone(&chopsticks[i]);
let mut right_fork = Arc::clone(&forks[(i + 1) % PHILOSOPHERS.len()]); let mut right_chopstick =
Arc::clone(&chopsticks[(i + 1) % PHILOSOPHERS.len()]);
if i == PHILOSOPHERS.len() - 1 { if i == PHILOSOPHERS.len() - 1 {
std::mem::swap(&mut left_fork, &mut right_fork); std::mem::swap(&mut left_chopstick, &mut right_chopstick);
} }
philosophers.push(Philosopher { philosophers.push(Philosopher {
name: name.to_string(), name: name.to_string(),
left_fork, left_chopstick,
right_fork, right_chopstick,
thoughts: tx.clone(), thoughts: tx.clone(),
}); });
} }

View File

@ -7,12 +7,13 @@ minutes: 20
The dining philosophers problem is a classic problem in concurrency: The dining philosophers problem is a classic problem in concurrency:
> Five philosophers dine together at the same table. Each philosopher has their > Five philosophers dine together at the same table. Each philosopher has their
> own place at the table. There is a fork between each plate. The dish served is > own place at the table. There is a chopstick between each plate. The dish
> a kind of spaghetti which has to be eaten with two forks. Each philosopher can > served is spaghetti which requires two chopsticks to eat. Each philosopher can
> only alternately think and eat. Moreover, a philosopher can only eat their > only alternately think and eat. Moreover, a philosopher can only eat their
> spaghetti when they have both a left and right fork. Thus two forks will only > spaghetti when they have both a left and right chopstick. Thus two chopsticks
> be available when their two nearest neighbors are thinking, not eating. After > will only be available when their two nearest neighbors are thinking, not
> an individual philosopher finishes eating, they will put down both forks. > eating. After an individual philosopher finishes eating, they will put down
> both chopsticks.
You will need a local [Cargo installation](../../cargo/running-locally.md) for You will need a local [Cargo installation](../../cargo/running-locally.md) for
this exercise. Copy the code below to a file called `src/main.rs`, fill out the this exercise. Copy the code below to a file called `src/main.rs`, fill out the
@ -22,17 +23,17 @@ blanks, and test that `cargo run` does not deadlock:
```rust,compile_fail ```rust,compile_fail
{{#include dining-philosophers.rs:Philosopher}} {{#include dining-philosophers.rs:Philosopher}}
// left_fork: ... // left_chopstick: ...
// right_fork: ... // right_chopstick: ...
// thoughts: ... // thoughts: ...
} }
{{#include dining-philosophers.rs:Philosopher-think}} {{#include dining-philosophers.rs:Philosopher-think}}
{{#include dining-philosophers.rs:Philosopher-eat}} {{#include dining-philosophers.rs:Philosopher-eat}}
// Pick up forks... // Pick up chopsticks...
{{#include dining-philosophers.rs:Philosopher-eat-end}} {{#include dining-philosophers.rs:Philosopher-eat-end}}
// Create forks // Create chopsticks
// Create philosophers // Create philosophers

View File

@ -18,13 +18,13 @@ use std::sync::{mpsc, Arc, Mutex};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
struct Fork; struct Chopstick;
struct Philosopher { struct Philosopher {
name: String, name: String,
// ANCHOR_END: Philosopher // ANCHOR_END: Philosopher
left_fork: Arc<Mutex<Fork>>, left_chopstick: Arc<Mutex<Chopstick>>,
right_fork: Arc<Mutex<Fork>>, right_chopstick: Arc<Mutex<Chopstick>>,
thoughts: mpsc::SyncSender<String>, thoughts: mpsc::SyncSender<String>,
} }
@ -41,8 +41,8 @@ impl Philosopher {
fn eat(&self) { fn eat(&self) {
// ANCHOR_END: Philosopher-eat // ANCHOR_END: Philosopher-eat
println!("{} is trying to eat", &self.name); println!("{} is trying to eat", &self.name);
let _left = self.left_fork.lock().unwrap(); let _left = self.left_chopstick.lock().unwrap();
let _right = self.right_fork.lock().unwrap(); let _right = self.right_chopstick.lock().unwrap();
// ANCHOR: Philosopher-eat-end // ANCHOR: Philosopher-eat-end
println!("{} is eating...", &self.name); println!("{} is eating...", &self.name);
@ -57,27 +57,29 @@ fn main() {
// ANCHOR_END: Philosopher-eat-end // ANCHOR_END: Philosopher-eat-end
let (tx, rx) = mpsc::sync_channel(10); let (tx, rx) = mpsc::sync_channel(10);
let forks = (0..PHILOSOPHERS.len()) let chopsticks = PHILOSOPHERS
.map(|_| Arc::new(Mutex::new(Fork))) .iter()
.map(|_| Arc::new(Mutex::new(Chopstick)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for i in 0..forks.len() { for i in 0..chopsticks.len() {
let tx = tx.clone(); let tx = tx.clone();
let mut left_fork = Arc::clone(&forks[i]); let mut left_chopstick = Arc::clone(&chopsticks[i]);
let mut right_fork = Arc::clone(&forks[(i + 1) % forks.len()]); let mut right_chopstick =
Arc::clone(&chopsticks[(i + 1) % chopsticks.len()]);
// To avoid a deadlock, we have to break the symmetry // To avoid a deadlock, we have to break the symmetry
// somewhere. This will swap the forks without deinitializing // somewhere. This will swap the chopsticks without deinitializing
// either of them. // either of them.
if i == forks.len() - 1 { if i == chopsticks.len() - 1 {
std::mem::swap(&mut left_fork, &mut right_fork); std::mem::swap(&mut left_chopstick, &mut right_chopstick);
} }
let philosopher = Philosopher { let philosopher = Philosopher {
name: PHILOSOPHERS[i].to_string(), name: PHILOSOPHERS[i].to_string(),
thoughts: tx, thoughts: tx,
left_fork, left_chopstick,
right_fork, right_chopstick,
}; };
thread::spawn(move || { thread::spawn(move || {