From 11571d0d1a0099319ea1d26b608d1be6fbfb20e5 Mon Sep 17 00:00:00 2001 From: rbehjati <razieh@google.com> Date: Tue, 9 May 2023 17:34:47 +0100 Subject: [PATCH] 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 --- src/SUMMARY.md | 5 +- src/exercises/concurrency/afternoon.md | 17 +++ .../concurrency/dining-philosophers-async.md | 57 ++++++++++ .../concurrency/dining-philosophers-async.rs | 103 ++++++++++++++++++ .../concurrency/solutions-afternoon.md | 10 ++ 5 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/exercises/concurrency/afternoon.md create mode 100644 src/exercises/concurrency/dining-philosophers-async.md create mode 100644 src/exercises/concurrency/dining-philosophers-async.rs create mode 100644 src/exercises/concurrency/solutions-afternoon.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c2b73649..d04259e6 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -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) diff --git a/src/exercises/concurrency/afternoon.md b/src/exercises/concurrency/afternoon.md new file mode 100644 index 00000000..3267a957 --- /dev/null +++ b/src/exercises/concurrency/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> diff --git a/src/exercises/concurrency/dining-philosophers-async.md b/src/exercises/concurrency/dining-philosophers-async.md new file mode 100644 index 00000000..64bf9577 --- /dev/null +++ b/src/exercises/concurrency/dining-philosophers-async.md @@ -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> diff --git a/src/exercises/concurrency/dining-philosophers-async.rs b/src/exercises/concurrency/dining-philosophers-async.rs new file mode 100644 index 00000000..f5a31765 --- /dev/null +++ b/src/exercises/concurrency/dining-philosophers-async.rs @@ -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}"); + } +} diff --git a/src/exercises/concurrency/solutions-afternoon.md b/src/exercises/concurrency/solutions-afternoon.md new file mode 100644 index 00000000..52d57bf4 --- /dev/null +++ b/src/exercises/concurrency/solutions-afternoon.md @@ -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}} +``` +