Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions module2/dining_philosophers/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[package]
name = "dining_philosophers"
version = "0.1.0"
edition = "2021"

[dependencies]
38 changes: 38 additions & 0 deletions module2/dining_philosophers/Makefile
Original file line number Diff line number Diff line change
@@ -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
134 changes: 134 additions & 0 deletions module2/dining_philosophers/src/main.rs
Original file line number Diff line number Diff line change
@@ -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<Fork>,
right_fork: Arc<Fork>,
}

impl Philosopher {
fn new(id: u32, name: &str, left_fork: Arc<Fork>, right_fork: Arc<Fork>) -> 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::<Vec<_>>();

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::<Vec<_>>();

let start = Instant::now();

let handles = philosophers
.into_iter()
.map(|philosopher| {
thread::spawn(move || {
philosopher.eat();
})
})
.collect::<Vec<_>>();

for handle in handles {
handle.join().unwrap();
}

println!("Total time: {:?}", start.elapsed());
}
Loading