Skip to content

Commit 1465985

Browse files
authored
Merge pull request #9 from JadeCara/jade/threading_philosophers
threaded dining
2 parents 4d49ddf + 34ccc0f commit 1465985

File tree

3 files changed

+178
-0
lines changed

3 files changed

+178
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "dining_philosophers"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
SHELL := /bin/bash
2+
.PHONY: help
3+
4+
help:
5+
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
6+
7+
clean: ## Clean the project using cargo
8+
cargo clean
9+
10+
build: ## Build the project using cargo
11+
cargo build
12+
13+
run: ## Run the project using cargo
14+
cargo run
15+
16+
test: ## Run the tests using cargo
17+
cargo test
18+
19+
lint: ## Run the linter using cargo
20+
@rustup component add clippy 2> /dev/null
21+
cargo clippy
22+
23+
format: ## Format the code using cargo
24+
@rustup component add rustfmt 2> /dev/null
25+
cargo fmt
26+
27+
release:
28+
cargo build --release
29+
30+
all: format lint test run
31+
32+
bump: ## Bump the version of the project
33+
@echo "Current version is $(shell cargo pkgid | cut -d# -f2)"
34+
@read -p "Enter the new version: " version; \
35+
updated_version=$$(cargo pkgid | cut -d# -f2 | sed "s/$(shell cargo pkgid | cut -d# -f2)/$$version/"); \
36+
sed -i -E "s/^version = .*/version = \"$$updated_version\"/" Cargo.toml
37+
@echo "Version bumped to $$(cargo pkgid | cut -d# -f2)"
38+
rm Cargo.toml-e
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* The dining philosophers problem involves multiple threads needing
3+
* synchronized access to shared resources, risking deadlock.
4+
*
5+
* This code models philosophers as threads and forks as shared Mutex<()>
6+
* wrapped in Arc for thread-safe reference counting.
7+
*
8+
* To prevent deadlock from a "deadly embrace" of waiting for neighboring
9+
* forks, philosophers acquire lower numbered forks first. This breaks
10+
* symmetry and avoids circular waiting.
11+
*
12+
* The Mutexes provide exclusive fork access. The Arc allows sharing forks
13+
* between philosophers.
14+
*
15+
* The simulation prints start time, eating duration, and total time for
16+
* all philosophers. Total time approximately equals philosophers divided
17+
* by forks, as that number can eat concurrently.
18+
*
19+
* Key techniques:
20+
* - Used Mutex<()> to represent exclusive fork access
21+
* - Wrapped in Arc to share Mutexes between threads
22+
* - Numbered philosophers and acquire lower fork first
23+
* - Prints timing metrics for simulation
24+
*
25+
* There is diminishing returns with concurrency timing is very important to
26+
* establish if threading is worth the overhead.
27+
*/
28+
29+
use std::sync::{Arc, Mutex};
30+
use std::thread;
31+
use std::time::{Duration, Instant};
32+
33+
struct Fork {
34+
id: u32,
35+
mutex: Mutex<()>,
36+
}
37+
38+
struct Philosopher {
39+
id: u32,
40+
name: String,
41+
left_fork: Arc<Fork>,
42+
right_fork: Arc<Fork>,
43+
}
44+
45+
impl Philosopher {
46+
fn new(id: u32, name: &str, left_fork: Arc<Fork>, right_fork: Arc<Fork>) -> Philosopher {
47+
Philosopher {
48+
id,
49+
name: name.to_string(),
50+
left_fork,
51+
right_fork,
52+
}
53+
}
54+
55+
fn eat(&self) {
56+
let (first_fork, second_fork) = if self.id % 2 == 0 {
57+
(&self.left_fork, &self.right_fork)
58+
} else {
59+
(&self.right_fork, &self.left_fork)
60+
};
61+
62+
let _first_guard = first_fork.mutex.lock().unwrap();
63+
println!("{} picked up fork {}.", self.name, first_fork.id);
64+
let _second_guard = second_fork.mutex.lock().unwrap();
65+
println!("{} picked up fork {}.", self.name, second_fork.id);
66+
67+
println!("{} is eating.", self.name);
68+
thread::sleep(Duration::from_secs(1));
69+
println!("{} finished eating.", self.name);
70+
71+
println!("{} put down fork {}.", self.name, first_fork.id);
72+
println!("{} put down fork {}.", self.name, second_fork.id);
73+
}
74+
}
75+
76+
fn main() {
77+
println!("Dining Philosophers Problem: 15 Philosophers, 4 Forks...Yikes!!");
78+
79+
//we only have 4 forks at the table
80+
let forks = (0..4)
81+
.map(|id| {
82+
Arc::new(Fork {
83+
id,
84+
mutex: Mutex::new(()),
85+
})
86+
})
87+
.collect::<Vec<_>>();
88+
89+
let philosophers = vec![
90+
("Jürgen Habermas", 0, 1),
91+
("Friedrich Engels", 1, 2),
92+
("Karl Marx", 2, 3),
93+
("Thomas Piketty", 3, 0),
94+
("Michel Foucault", 0, 1),
95+
("Socrates", 1, 2),
96+
("Plato", 2, 3),
97+
("Aristotle", 3, 0),
98+
("Pythagoras", 0, 1),
99+
("Heraclitus", 1, 2),
100+
("Democritus", 2, 3),
101+
("Diogenes", 3, 0),
102+
("Epicurus", 0, 1),
103+
("Zeno of Citium", 1, 2),
104+
("Thales of Miletus", 2, 3),
105+
]
106+
.into_iter()
107+
.enumerate()
108+
.map(|(id, (name, left, right))| {
109+
Philosopher::new(
110+
id as u32,
111+
name,
112+
Arc::clone(&forks[left]),
113+
Arc::clone(&forks[right]),
114+
)
115+
})
116+
.collect::<Vec<_>>();
117+
118+
let start = Instant::now();
119+
120+
let handles = philosophers
121+
.into_iter()
122+
.map(|philosopher| {
123+
thread::spawn(move || {
124+
philosopher.eat();
125+
})
126+
})
127+
.collect::<Vec<_>>();
128+
129+
for handle in handles {
130+
handle.join().unwrap();
131+
}
132+
133+
println!("Total time: {:?}", start.elapsed());
134+
}

0 commit comments

Comments
 (0)