Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use chopsticks and limit philosophers. #2655

Merged
merged 2 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/concurrency/async-exercises/dining-philosophers.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ code below to a file called `src/main.rs`, fill out the blanks, and test that

```rust,compile_fail
{{#include dining-philosophers.rs:Philosopher}}
// left_fork: ...
// right_fork: ...
// left_chopstick: ...
// right_chopstick: ...
// thoughts: ...
}
Expand All @@ -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-body}}
{{#include dining-philosophers.rs:Philosopher-eat-end}}
// Create forks
// Create chopsticks
// Create philosophers
Expand Down
37 changes: 20 additions & 17 deletions src/concurrency/async-exercises/dining-philosophers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ use std::sync::Arc;
use tokio::sync::{mpsc, Mutex};
use tokio::time;

struct Fork;
struct Chopstick;

struct Philosopher {
name: String,
// ANCHOR_END: Philosopher
left_fork: Arc<Mutex<Fork>>,
right_fork: Arc<Mutex<Fork>>,
left_chopstick: Arc<Mutex<Chopstick>>,
right_chopstick: Arc<Mutex<Chopstick>>,
thoughts: mpsc::Sender<String>,
}

Expand All @@ -40,11 +40,11 @@ impl Philosopher {

// ANCHOR: Philosopher-eat
async fn eat(&self) {
// Keep trying until we have both forks
// Keep trying until we have both chopsticks
// ANCHOR_END: Philosopher-eat
// Pick up forks...
let _left_fork = self.left_fork.lock().await;
let _right_fork = self.right_fork.lock().await;
// Pick up chopsticks...
let _left_chopstick = self.left_chopstick.lock().await;
let _right_chopstick = self.right_chopstick.lock().await;

// ANCHOR: Philosopher-eat-body
println!("{} is eating...", &self.name);
Expand All @@ -56,30 +56,33 @@ impl Philosopher {
}
}

static PHILOSOPHERS: &[&str] =
&["Socrates", "Hypatia", "Plato", "Aristotle", "Pythagoras"];
// tokio scheduler doesn't deadlock with 5 philosophers, so have 2.
static PHILOSOPHERS: &[&str] = &["Socrates", "Hypatia"];

#[tokio::main]
async fn main() {
// ANCHOR_END: Philosopher-eat-end
// Create forks
let mut forks = vec![];
(0..PHILOSOPHERS.len()).for_each(|_| forks.push(Arc::new(Mutex::new(Fork))));
// Create chopsticks
let mut chopsticks = vec![];
PHILOSOPHERS
.iter()
.for_each(|_| chopsticks.push(Arc::new(Mutex::new(Chopstick))));

// Create philosophers
let (philosophers, mut rx) = {
let mut philosophers = vec![];
let (tx, rx) = mpsc::channel(10);
for (i, name) in PHILOSOPHERS.iter().enumerate() {
let mut left_fork = Arc::clone(&forks[i]);
let mut right_fork = Arc::clone(&forks[(i + 1) % PHILOSOPHERS.len()]);
let mut left_chopstick = Arc::clone(&chopsticks[i]);
let mut right_chopstick =
Arc::clone(&chopsticks[(i + 1) % PHILOSOPHERS.len()]);
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 {
name: name.to_string(),
left_fork,
right_fork,
left_chopstick,
right_chopstick,
thoughts: tx.clone(),
});
}
Expand Down
19 changes: 10 additions & 9 deletions src/concurrency/sync-exercises/dining-philosophers.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ minutes: 20
The dining philosophers problem is a classic problem in concurrency:

> 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
> a kind of spaghetti which has to be eaten with two forks. Each philosopher can
> own place at the table. There is a chopstick between each plate. The dish
> served is spaghetti which requires two chopsticks to eat. Each philosopher can
> 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
> be available when their two nearest neighbors are thinking, not eating. After
> an individual philosopher finishes eating, they will put down both forks.
> spaghetti when they have both a left and right chopstick. Thus two chopsticks
> will only be available when their two nearest neighbors are thinking, not
> 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
this exercise. Copy the code below to a file called `src/main.rs`, fill out the
Expand All @@ -22,17 +23,17 @@ blanks, and test that `cargo run` does not deadlock:

```rust,compile_fail
{{#include dining-philosophers.rs:Philosopher}}
// left_fork: ...
// right_fork: ...
// left_chopstick: ...
// right_chopstick: ...
// thoughts: ...
}
{{#include dining-philosophers.rs:Philosopher-think}}
{{#include dining-philosophers.rs:Philosopher-eat}}
// Pick up forks...
// Pick up chopsticks...
{{#include dining-philosophers.rs:Philosopher-eat-end}}
// Create forks
// Create chopsticks
// Create philosophers
Expand Down
32 changes: 17 additions & 15 deletions src/concurrency/sync-exercises/dining-philosophers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ use std::sync::{mpsc, Arc, Mutex};
use std::thread;
use std::time::Duration;

struct Fork;
struct Chopstick;

struct Philosopher {
name: String,
// ANCHOR_END: Philosopher
left_fork: Arc<Mutex<Fork>>,
right_fork: Arc<Mutex<Fork>>,
left_chopstick: Arc<Mutex<Chopstick>>,
right_chopstick: Arc<Mutex<Chopstick>>,
thoughts: mpsc::SyncSender<String>,
}

Expand All @@ -41,8 +41,8 @@ impl Philosopher {
fn eat(&self) {
// ANCHOR_END: Philosopher-eat
println!("{} is trying to eat", &self.name);
let _left = self.left_fork.lock().unwrap();
let _right = self.right_fork.lock().unwrap();
let _left = self.left_chopstick.lock().unwrap();
let _right = self.right_chopstick.lock().unwrap();

// ANCHOR: Philosopher-eat-end
println!("{} is eating...", &self.name);
Expand All @@ -57,27 +57,29 @@ fn main() {
// ANCHOR_END: Philosopher-eat-end
let (tx, rx) = mpsc::sync_channel(10);

let forks = (0..PHILOSOPHERS.len())
.map(|_| Arc::new(Mutex::new(Fork)))
let chopsticks = PHILOSOPHERS
.iter()
.map(|_| Arc::new(Mutex::new(Chopstick)))
.collect::<Vec<_>>();

for i in 0..forks.len() {
for i in 0..chopsticks.len() {
let tx = tx.clone();
let mut left_fork = Arc::clone(&forks[i]);
let mut right_fork = Arc::clone(&forks[(i + 1) % forks.len()]);
let mut left_chopstick = Arc::clone(&chopsticks[i]);
let mut right_chopstick =
Arc::clone(&chopsticks[(i + 1) % chopsticks.len()]);

// 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.
if i == forks.len() - 1 {
std::mem::swap(&mut left_fork, &mut right_fork);
if i == chopsticks.len() - 1 {
std::mem::swap(&mut left_chopstick, &mut right_chopstick);
}

let philosopher = Philosopher {
name: PHILOSOPHERS[i].to_string(),
thoughts: tx,
left_fork,
right_fork,
left_chopstick,
right_chopstick,
};

thread::spawn(move || {
Expand Down