mirror of
https://github.com/google/comprehensive-rust.git
synced 2025-03-25 23:49:54 +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)
|
- [Blocking the Executor](async/pitfalls/blocking-executor.md)
|
||||||
- [Pin](async/pitfalls/pin.md)
|
- [Pin](async/pitfalls/pin.md)
|
||||||
- [Async Traits](async/pitfalls/async-traits.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
|
# Final Words
|
||||||
@ -316,3 +318,4 @@
|
|||||||
- [Bare Metal Rust Morning](exercises/bare-metal/solutions-morning.md)
|
- [Bare Metal Rust Morning](exercises/bare-metal/solutions-morning.md)
|
||||||
- [Bare Metal Rust Afternoon](exercises/bare-metal/solutions-afternoon.md)
|
- [Bare Metal Rust Afternoon](exercises/bare-metal/solutions-afternoon.md)
|
||||||
- [Concurrency Morning](exercises/concurrency/solutions-morning.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