diff --git a/module2/dining_philosophers/Cargo.toml b/module2/dining_philosophers/Cargo.toml new file mode 100644 index 0000000..14a659e --- /dev/null +++ b/module2/dining_philosophers/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "dining_philosophers" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/module2/dining_philosophers/Makefile b/module2/dining_philosophers/Makefile new file mode 100644 index 0000000..4daa6f8 --- /dev/null +++ b/module2/dining_philosophers/Makefile @@ -0,0 +1,38 @@ +SHELL := /bin/bash +.PHONY: help + +help: + @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +clean: ## Clean the project using cargo + cargo clean + +build: ## Build the project using cargo + cargo build + +run: ## Run the project using cargo + cargo run + +test: ## Run the tests using cargo + cargo test + +lint: ## Run the linter using cargo + @rustup component add clippy 2> /dev/null + cargo clippy + +format: ## Format the code using cargo + @rustup component add rustfmt 2> /dev/null + cargo fmt + +release: + cargo build --release + +all: format lint test run + +bump: ## Bump the version of the project + @echo "Current version is $(shell cargo pkgid | cut -d# -f2)" + @read -p "Enter the new version: " version; \ + updated_version=$$(cargo pkgid | cut -d# -f2 | sed "s/$(shell cargo pkgid | cut -d# -f2)/$$version/"); \ + sed -i -E "s/^version = .*/version = \"$$updated_version\"/" Cargo.toml + @echo "Version bumped to $$(cargo pkgid | cut -d# -f2)" + rm Cargo.toml-e \ No newline at end of file diff --git a/module2/dining_philosophers/src/main.rs b/module2/dining_philosophers/src/main.rs new file mode 100644 index 0000000..d27353e --- /dev/null +++ b/module2/dining_philosophers/src/main.rs @@ -0,0 +1,134 @@ +/* +* The dining philosophers problem involves multiple threads needing +* synchronized access to shared resources, risking deadlock. +* +* This code models philosophers as threads and forks as shared Mutex<()> +* wrapped in Arc for thread-safe reference counting. +* +* To prevent deadlock from a "deadly embrace" of waiting for neighboring +* forks, philosophers acquire lower numbered forks first. This breaks +* symmetry and avoids circular waiting. +* +* The Mutexes provide exclusive fork access. The Arc allows sharing forks +* between philosophers. +* +* The simulation prints start time, eating duration, and total time for +* all philosophers. Total time approximately equals philosophers divided +* by forks, as that number can eat concurrently. +* +* Key techniques: +* - Used Mutex<()> to represent exclusive fork access +* - Wrapped in Arc to share Mutexes between threads +* - Numbered philosophers and acquire lower fork first +* - Prints timing metrics for simulation +* +* There is diminishing returns with concurrency timing is very important to +* establish if threading is worth the overhead. +*/ + +use std::sync::{Arc, Mutex}; +use std::thread; +use std::time::{Duration, Instant}; + +struct Fork { + id: u32, + mutex: Mutex<()>, +} + +struct Philosopher { + id: u32, + name: String, + left_fork: Arc, + right_fork: Arc, +} + +impl Philosopher { + fn new(id: u32, name: &str, left_fork: Arc, right_fork: Arc) -> Philosopher { + Philosopher { + id, + name: name.to_string(), + left_fork, + right_fork, + } + } + + fn eat(&self) { + let (first_fork, second_fork) = if self.id % 2 == 0 { + (&self.left_fork, &self.right_fork) + } else { + (&self.right_fork, &self.left_fork) + }; + + let _first_guard = first_fork.mutex.lock().unwrap(); + println!("{} picked up fork {}.", self.name, first_fork.id); + let _second_guard = second_fork.mutex.lock().unwrap(); + println!("{} picked up fork {}.", self.name, second_fork.id); + + println!("{} is eating.", self.name); + thread::sleep(Duration::from_secs(1)); + println!("{} finished eating.", self.name); + + println!("{} put down fork {}.", self.name, first_fork.id); + println!("{} put down fork {}.", self.name, second_fork.id); + } +} + +fn main() { + println!("Dining Philosophers Problem: 15 Philosophers, 4 Forks...Yikes!!"); + + //we only have 4 forks at the table + let forks = (0..4) + .map(|id| { + Arc::new(Fork { + id, + mutex: Mutex::new(()), + }) + }) + .collect::>(); + + let philosophers = vec![ + ("Jürgen Habermas", 0, 1), + ("Friedrich Engels", 1, 2), + ("Karl Marx", 2, 3), + ("Thomas Piketty", 3, 0), + ("Michel Foucault", 0, 1), + ("Socrates", 1, 2), + ("Plato", 2, 3), + ("Aristotle", 3, 0), + ("Pythagoras", 0, 1), + ("Heraclitus", 1, 2), + ("Democritus", 2, 3), + ("Diogenes", 3, 0), + ("Epicurus", 0, 1), + ("Zeno of Citium", 1, 2), + ("Thales of Miletus", 2, 3), + ] + .into_iter() + .enumerate() + .map(|(id, (name, left, right))| { + Philosopher::new( + id as u32, + name, + Arc::clone(&forks[left]), + Arc::clone(&forks[right]), + ) + }) + .collect::>(); + + let start = Instant::now(); + + let handles = philosophers + .into_iter() + .map(|philosopher| { + thread::spawn(move || { + philosopher.eat(); + }) + }) + .collect::>(); + + for handle in handles { + handle.join().unwrap(); + } + + println!("Total time: {:?}", start.elapsed()); +}