mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-03-22 23:05:22 +02:00
Add dining philosophers as an async exercise (#624)
* Adds dining philosophers as an async exercise * Adds a solution for async dining philosophers * Adds a solution page for the afternoon session on concurrency
This commit is contained in:
parent
4c91965224
commit
11571d0d1a
@ -290,7 +290,9 @@
|
||||
- [Blocking the Executor](async/pitfalls/blocking-executor.md)
|
||||
- [Pin](async/pitfalls/pin.md)
|
||||
- [Async Traits](async/pitfalls/async-traits.md)
|
||||
- [Exercises](exercises/concurrency/elevator.md)
|
||||
- [Exercises](exercises/concurrency/afternoon.md)
|
||||
- [Dining Philosophers](exercises/concurrency/dining-philosophers-async.md)
|
||||
- [Elevator Operations](exercises/concurrency/elevator.md)
|
||||
|
||||
|
||||
# Final Words
|
||||
@ -316,3 +318,4 @@
|
||||
- [Bare Metal Rust Morning](exercises/bare-metal/solutions-morning.md)
|
||||
- [Bare Metal Rust Afternoon](exercises/bare-metal/solutions-afternoon.md)
|
||||
- [Concurrency Morning](exercises/concurrency/solutions-morning.md)
|
||||
- [Concurrency Afternoon](exercises/concurrency/solutions-afternoon.md)
|
||||
|
17
src/exercises/concurrency/afternoon.md
Normal file
17
src/exercises/concurrency/afternoon.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Exercises
|
||||
|
||||
To practice your Async Rust skills, we have again two exercises for you:
|
||||
|
||||
* Dining philosophers: we already saw this problem in the morning. This time
|
||||
you are going to implement it with Async Rust.
|
||||
|
||||
* The Elevator Problem: this is a larger project that allows you experiment
|
||||
with more advanced Async Rust features and some of its pitfalls!
|
||||
|
||||
<details>
|
||||
|
||||
After looking at the exercises, you can look at the [solutions] provided.
|
||||
|
||||
[solutions]: solutions-afternoon.md
|
||||
|
||||
</details>
|
57
src/exercises/concurrency/dining-philosophers-async.md
Normal file
57
src/exercises/concurrency/dining-philosophers-async.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Dining Philosophers - Async
|
||||
|
||||
See [dining philosophers](dining-philosophers.md) for a description of the
|
||||
problem.
|
||||
|
||||
As before, 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 blanks, and test
|
||||
that `cargo run` does not deadlock:
|
||||
|
||||
<!-- File src/main.rs -->
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include dining-philosophers-async.rs:Philosopher}}
|
||||
// left_fork: ...
|
||||
// right_fork: ...
|
||||
// thoughts: ...
|
||||
}
|
||||
|
||||
{{#include dining-philosophers-async.rs:Philosopher-think}}
|
||||
|
||||
{{#include dining-philosophers-async.rs:Philosopher-eat}}
|
||||
{{#include dining-philosophers-async.rs:Philosopher-eat-body}}
|
||||
{{#include dining-philosophers-async.rs:Philosopher-eat-end}}
|
||||
// Create forks
|
||||
|
||||
// Create philosophers
|
||||
|
||||
// Make them think and eat
|
||||
|
||||
// Output their thoughts
|
||||
}
|
||||
```
|
||||
|
||||
Since this time you are using Async Rust, you'll need a `tokio` dependency.
|
||||
You can use the following `Cargo.toml`:
|
||||
|
||||
<!-- File Cargo.toml -->
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "dining-philosophers-async-dine"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = {version = "1.26.0", features = ["sync", "time", "macros", "rt-multi-thread"]}
|
||||
```
|
||||
|
||||
Also note that this time you have to use the `Mutex` and the `mpsc` module
|
||||
form the `tokio` crate.
|
||||
|
||||
<details>
|
||||
|
||||
* Can you make your implementation single-threaded?
|
||||
|
||||
</details>
|
103
src/exercises/concurrency/dining-philosophers-async.rs
Normal file
103
src/exercises/concurrency/dining-philosophers-async.rs
Normal file
@ -0,0 +1,103 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// ANCHOR: Philosopher
|
||||
use std::sync::Arc;
|
||||
use tokio::time;
|
||||
use tokio::sync::mpsc::{self, Sender};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
struct Fork;
|
||||
|
||||
struct Philosopher {
|
||||
name: String,
|
||||
// ANCHOR_END: Philosopher
|
||||
left_fork: Arc<Mutex<Fork>>,
|
||||
right_fork: Arc<Mutex<Fork>>,
|
||||
thoughts: Sender<String>,
|
||||
}
|
||||
|
||||
// ANCHOR: Philosopher-think
|
||||
impl Philosopher {
|
||||
async fn think(&self) {
|
||||
self.thoughts
|
||||
.send(format!("Eureka! {} has a new idea!", &self.name)).await
|
||||
.unwrap();
|
||||
}
|
||||
// ANCHOR_END: Philosopher-think
|
||||
|
||||
// ANCHOR: Philosopher-eat
|
||||
async fn eat(&self) {
|
||||
// Pick up forks...
|
||||
// ANCHOR_END: Philosopher-eat
|
||||
let _first_lock = self.left_fork.lock().await;
|
||||
// Add a delay before picking the second fork to allow the execution
|
||||
// to transfer to another task
|
||||
time::sleep(time::Duration::from_millis(1)).await;
|
||||
let _second_lock = self.right_fork.lock().await;
|
||||
|
||||
// ANCHOR: Philosopher-eat-body
|
||||
println!("{} is eating...", &self.name);
|
||||
time::sleep(time::Duration::from_millis(5)).await;
|
||||
// ANCHOR_END: Philosopher-eat-body
|
||||
|
||||
// The locks are dropped here
|
||||
// ANCHOR: Philosopher-eat-end
|
||||
}
|
||||
}
|
||||
|
||||
static PHILOSOPHERS: &[&str] =
|
||||
&["Socrates", "Plato", "Aristotle", "Thales", "Pythagoras"];
|
||||
|
||||
#[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 philosophers
|
||||
let (philosophers, mut rx) = {
|
||||
let mut philosophers = vec![];
|
||||
let (tx, rx) = mpsc::channel(10);
|
||||
for (i, name) in PHILOSOPHERS.iter().enumerate() {
|
||||
let left_fork = forks[i].clone();
|
||||
let right_fork = forks[(i + 1) % PHILOSOPHERS.len()].clone();
|
||||
philosophers.push(Philosopher {
|
||||
name: name.to_string(),
|
||||
left_fork: if i % 2 == 0 { left_fork.clone() } else { right_fork.clone() },
|
||||
right_fork: if i % 2 == 0 { right_fork } else { left_fork },
|
||||
thoughts: tx.clone(),
|
||||
});
|
||||
}
|
||||
(philosophers, rx)
|
||||
// tx is dropped here, so we don't need to explicitly drop it later
|
||||
};
|
||||
|
||||
// Make them think and eat
|
||||
for phil in philosophers {
|
||||
tokio::spawn(async move {
|
||||
for _ in 0..100 {
|
||||
phil.think().await;
|
||||
phil.eat().await;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Output their thoughts
|
||||
while let Some(thought) = rx.recv().await {
|
||||
println!("Here is a thought: {thought}");
|
||||
}
|
||||
}
|
10
src/exercises/concurrency/solutions-afternoon.md
Normal file
10
src/exercises/concurrency/solutions-afternoon.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Concurrency Afternoon Exercise
|
||||
|
||||
## Dining Philosophers - Async
|
||||
|
||||
([back to exercise](dining-philosophers-async.md))
|
||||
|
||||
```rust,compile_fail
|
||||
{{#include dining-philosophers-async.rs}}
|
||||
```
|
||||
|
Loading…
x
Reference in New Issue
Block a user