Skip to content

Commit

Permalink
Merge pull request #9 from JadeCara/jade/threading_philosophers
Browse files Browse the repository at this point in the history
threaded dining
  • Loading branch information
JadeCara authored Dec 16, 2024
2 parents 4d49ddf + 34ccc0f commit 1465985
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 0 deletions.
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());
}

0 comments on commit 1465985

Please sign in to comment.