1
0
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 ()

* 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:
rbehjati 2023-05-09 17:34:47 +01:00 committed by GitHub
parent 4c91965224
commit 11571d0d1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 191 additions and 1 deletions

@ -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)

@ -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>

@ -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>

@ -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}");
}
}

@ -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}}
```