2
2
3
3
For our second project, let’s look at a classic concurrency problem. It’s
4
4
called ‘the dining philosophers’. It was originally conceived by Dijkstra in
5
- 1965, but we’ll use the version from [ this paper] [ paper ] by Tony Hoare in 1985.
6
-
7
- [ paper ] : http://www.usingcsp.com/cspbook.pdf
8
-
9
- > In ancient times, a wealthy philanthropist endowed a College to accommodate
10
- > five eminent philosophers. Each philosopher had a room in which she could
11
- > engage in her professional activity of thinking; there was also a common
12
- > dining room, furnished with a circular table, surrounded by five chairs, each
13
- > labelled by the name of the philosopher who was to sit in it. They sat
14
- > anticlockwise around the table. To the left of each philosopher there was
15
- > laid a golden fork, and in the centre stood a large bowl of spaghetti, which
16
- > was constantly replenished. A philosopher was expected to spend most of her
17
- > time thinking; but when she felt hungry, she went to the dining room, sat down
18
- > in her own chair, picked up her own fork on her left, and plunged it into the
19
- > spaghetti. But such is the tangled nature of spaghetti that a second fork is
20
- > required to carry it to the mouth. The philosopher therefore had also to pick
21
- > up the fork on her right. When she was finished she would put down both her
22
- > forks, get up from her chair, and continue thinking. Of course, a fork can be
23
- > used by only one philosopher at a time. If the other philosopher wants it, she
24
- > just has to wait until the fork is available again.
5
+ 1965, and has been used as an example of the use of concurrency primitives in
6
+ many influential works since then.
7
+
8
+ Plato, Hypatia, Lao, Simone, and Audre are professors of philosophy at a
9
+ university. As philosophers, they spend most of their time in their offices
10
+ thinking and writing; but occasionally, they need to come out for food. There
11
+ is a round table in the common room between their offices, and they each have
12
+ a designated seat at it; the university ensures that there is always a fresh
13
+ bowl of rice at each seat, so that they may come out and dine whenever they
14
+ please. However, due to budgetary constraints, the university has only been
15
+ able to afford five chopsticks for the five professors; in order to ensure
16
+ that everyone can eat, the chopsticks are arranged between the places at the
17
+ table, so each professor can pick up the two adjacent chopstics and begin
18
+ eating. Whean each is done eating, they put down their chopsticks in the
19
+ original locations, and return to their studies.
25
20
26
21
This classic problem shows off a few different elements of concurrency. The
27
22
reason is that it's actually slightly tricky to implement: a simple
28
23
implementation can deadlock. For example, let's consider a simple algorithm
29
24
that would solve this problem:
30
25
31
- 1 . A philosopher picks up the fork on their left.
32
- 2 . They then pick up the fork on their right.
26
+ 1 . A philosopher picks up the chopstick on their left.
27
+ 2 . They then pick up the chopstick on their right.
33
28
3 . They eat.
34
- 4 . They return the forks .
29
+ 4 . They return the chopsticks .
35
30
36
31
Now, let’s imagine this sequence of events:
37
32
38
- 1 . Philosopher 1 begins the algorithm, picking up the fork on their left.
39
- 2 . Philosopher 2 begins the algorithm, picking up the fork on their left.
40
- 3 . Philosopher 3 begins the algorithm, picking up the fork on their left.
41
- 4 . Philosopher 4 begins the algorithm, picking up the fork on their left.
42
- 5 . Philosopher 5 begins the algorithm, picking up the fork on their left.
43
- 6 . ... ? All the forks are taken, but nobody can eat!
33
+ 1 . Plato begins the algorithm, picking up the chopstick on his left.
34
+ 2 . Hypatia begins the algorithm, picking up the chopstick on her left.
35
+ 3 . Lao begins the algorithm, picking up the chopstick on his left.
36
+ 4 . Simone begins the algorithm, picking up the chopstick on her left.
37
+ 5 . Audre begins the algorithm, picking up the chopstick on her left.
38
+ 6 . ... ? All the chopsticks are taken, but nobody can eat!
44
39
45
40
There are different ways to solve this problem. We’ll get to our solution in
46
41
the tutorial itself. For now, let’s get started modelling the problem itself.
@@ -60,11 +55,11 @@ impl Philosopher {
60
55
}
61
56
62
57
fn main () {
63
- let p1 = Philosopher :: new (" Baruch Spinoza " );
64
- let p2 = Philosopher :: new (" Gilles Deleuze " );
65
- let p3 = Philosopher :: new (" Karl Marx " );
66
- let p4 = Philosopher :: new (" Friedrich Nietzsche " );
67
- let p5 = Philosopher :: new (" Michel Foucault " );
58
+ let p1 = Philosopher :: new (" Plato " );
59
+ let p2 = Philosopher :: new (" Hypatia " );
60
+ let p3 = Philosopher :: new (" Lao " );
61
+ let p4 = Philosopher :: new (" Simone " );
62
+ let p5 = Philosopher :: new (" Audre " );
68
63
}
69
64
```
70
65
@@ -159,11 +154,11 @@ look at `main()` again:
159
154
# }
160
155
#
161
156
fn main () {
162
- let p1 = Philosopher :: new (" Baruch Spinoza " );
163
- let p2 = Philosopher :: new (" Gilles Deleuze " );
164
- let p3 = Philosopher :: new (" Karl Marx " );
165
- let p4 = Philosopher :: new (" Friedrich Nietzsche " );
166
- let p5 = Philosopher :: new (" Michel Foucault " );
157
+ let p1 = Philosopher :: new (" Plato " );
158
+ let p2 = Philosopher :: new (" Hypatia " );
159
+ let p3 = Philosopher :: new (" Lao " );
160
+ let p4 = Philosopher :: new (" Simone " );
161
+ let p5 = Philosopher :: new (" Audre " );
167
162
}
168
163
```
169
164
@@ -176,11 +171,11 @@ that `new()` function, it would look like this:
176
171
# name : String ,
177
172
# }
178
173
fn main () {
179
- let p1 = Philosopher { name : " Baruch Spinoza " . to_string () };
180
- let p2 = Philosopher { name : " Gilles Deleuze " . to_string () };
181
- let p3 = Philosopher { name : " Karl Marx " . to_string () };
174
+ let p1 = Philosopher { name : " Plato " . to_string () };
175
+ let p2 = Philosopher { name : " Hypatia " . to_string () };
176
+ let p3 = Philosopher { name : " Lao " . to_string () };
182
177
let p4 = Philosopher { name : " Friedrich Nietzche" . to_string () };
183
- let p5 = Philosopher { name : " Michel Foucault " . to_string () };
178
+ let p5 = Philosopher { name : " Audre " . to_string () };
184
179
}
185
180
```
186
181
@@ -211,11 +206,11 @@ impl Philosopher {
211
206
212
207
fn main () {
213
208
let philosophers = vec! [
214
- Philosopher :: new (" Baruch Spinoza " ),
215
- Philosopher :: new (" Gilles Deleuze " ),
216
- Philosopher :: new (" Karl Marx " ),
217
- Philosopher :: new (" Friedrich Nietzsche " ),
218
- Philosopher :: new (" Michel Foucault " ),
209
+ Philosopher :: new (" Plato " ),
210
+ Philosopher :: new (" Hypatia " ),
211
+ Philosopher :: new (" Lao " ),
212
+ Philosopher :: new (" Simone " ),
213
+ Philosopher :: new (" Audre " ),
219
214
];
220
215
221
216
for p in & philosophers {
@@ -247,11 +242,11 @@ mention they’re done eating. Running this program should give you the followin
247
242
output:
248
243
249
244
``` text
250
- Baruch Spinoza is done eating.
251
- Gilles Deleuze is done eating.
252
- Karl Marx is done eating.
253
- Friedrich Nietzsche is done eating.
254
- Michel Foucault is done eating.
245
+ Plato is done eating.
246
+ Hypatia is done eating.
247
+ Lao is done eating.
248
+ Simone is done eating.
249
+ Audre is done eating.
255
250
```
256
251
257
252
Easy enough, they’re all done! We haven’t actually implemented the real problem
@@ -285,11 +280,11 @@ impl Philosopher {
285
280
286
281
fn main () {
287
282
let philosophers = vec! [
288
- Philosopher :: new (" Baruch Spinoza " ),
289
- Philosopher :: new (" Gilles Deleuze " ),
290
- Philosopher :: new (" Karl Marx " ),
291
- Philosopher :: new (" Friedrich Nietzsche " ),
292
- Philosopher :: new (" Michel Foucault " ),
283
+ Philosopher :: new (" Plato " ),
284
+ Philosopher :: new (" Hypatia " ),
285
+ Philosopher :: new (" Lao " ),
286
+ Philosopher :: new (" Simone " ),
287
+ Philosopher :: new (" Audre " ),
293
288
];
294
289
295
290
for p in & philosophers {
@@ -323,16 +318,16 @@ simulate the time it takes a philosopher to eat.
323
318
If you run this program, you should see each philosopher eat in turn:
324
319
325
320
``` text
326
- Baruch Spinoza is eating.
327
- Baruch Spinoza is done eating.
328
- Gilles Deleuze is eating.
329
- Gilles Deleuze is done eating.
330
- Karl Marx is eating.
331
- Karl Marx is done eating.
332
- Friedrich Nietzsche is eating.
333
- Friedrich Nietzsche is done eating.
334
- Michel Foucault is eating.
335
- Michel Foucault is done eating.
321
+ Plato is eating.
322
+ Plato is done eating.
323
+ Hypatia is eating.
324
+ Hypatia is done eating.
325
+ Lao is eating.
326
+ Lao is done eating.
327
+ Simone is eating.
328
+ Simone is done eating.
329
+ Audre is eating.
330
+ Audre is done eating.
336
331
```
337
332
338
333
Excellent! We’re getting there. There’s just one problem: we aren’t actually
@@ -366,11 +361,11 @@ impl Philosopher {
366
361
367
362
fn main () {
368
363
let philosophers = vec! [
369
- Philosopher :: new (" Baruch Spinoza " ),
370
- Philosopher :: new (" Gilles Deleuze " ),
371
- Philosopher :: new (" Karl Marx " ),
372
- Philosopher :: new (" Friedrich Nietzsche " ),
373
- Philosopher :: new (" Michel Foucault " ),
364
+ Philosopher :: new (" Plato " ),
365
+ Philosopher :: new (" Hypatia " ),
366
+ Philosopher :: new (" Lao " ),
367
+ Philosopher :: new (" Simone " ),
368
+ Philosopher :: new (" Audre " ),
374
369
];
375
370
376
371
let handles : Vec <_ > = philosophers . into_iter (). map (| p | {
@@ -456,33 +451,33 @@ If you run this program, you’ll see that the philosophers eat out of order!
456
451
We have multi-threading!
457
452
458
453
``` text
459
- Gilles Deleuze is eating.
460
- Gilles Deleuze is done eating.
461
- Friedrich Nietzsche is eating.
462
- Friedrich Nietzsche is done eating.
463
- Michel Foucault is eating.
464
- Baruch Spinoza is eating.
465
- Baruch Spinoza is done eating.
466
- Karl Marx is eating.
467
- Karl Marx is done eating.
468
- Michel Foucault is done eating.
454
+ Hypatia is eating.
455
+ Hypatia is done eating.
456
+ Simone is eating.
457
+ Simone is done eating.
458
+ Audre is eating.
459
+ Plato is eating.
460
+ Plato is done eating.
461
+ Lao is eating.
462
+ Lao is done eating.
463
+ Audre is done eating.
469
464
```
470
465
471
- But what about the forks ? We haven’t modeled them at all yet.
466
+ But what about the chopsticks ? We haven’t modeled them at all yet.
472
467
473
468
To do that, let’s make a new ` struct ` :
474
469
475
470
``` rust
476
471
use std :: sync :: Mutex ;
477
472
478
473
struct Table {
479
- forks : Vec <Mutex <()>>,
474
+ chopsticks : Vec <Mutex <()>>,
480
475
}
481
476
```
482
477
483
478
This ` Table ` has a vector of ` Mutex ` es. A mutex is a way to control
484
479
concurrency: only one thread can access the contents at once. This is exactly
485
- the property we need with our forks . We use an empty tuple, ` () ` , inside the
480
+ the property we need with our chopsticks . We use an empty tuple, ` () ` , inside the
486
481
mutex, since we’re not actually going to use the value, just hold onto it.
487
482
488
483
Let’s modify the program to use the ` Table ` :
@@ -507,8 +502,8 @@ impl Philosopher {
507
502
}
508
503
509
504
fn eat (& self , table : & Table ) {
510
- let _left = table . forks [self . left]. lock (). unwrap ();
511
- let _right = table . forks [self . right]. lock (). unwrap ();
505
+ let _left = table . chopsticks [self . left]. lock (). unwrap ();
506
+ let _right = table . chopsticks [self . right]. lock (). unwrap ();
512
507
513
508
println! (" {} is eating." , self . name);
514
509
@@ -519,11 +514,11 @@ impl Philosopher {
519
514
}
520
515
521
516
struct Table {
522
- forks : Vec <Mutex <()>>,
517
+ chopsticks : Vec <Mutex <()>>,
523
518
}
524
519
525
520
fn main () {
526
- let table = Arc :: new (Table { forks : vec! [
521
+ let table = Arc :: new (Table { chopsticks : vec! [
527
522
Mutex :: new (()),
528
523
Mutex :: new (()),
529
524
Mutex :: new (()),
@@ -532,11 +527,11 @@ fn main() {
532
527
]});
533
528
534
529
let philosophers = vec! [
535
- Philosopher :: new (" Baruch Spinoza " , 0 , 1 ),
536
- Philosopher :: new (" Gilles Deleuze " , 1 , 2 ),
537
- Philosopher :: new (" Karl Marx " , 2 , 3 ),
538
- Philosopher :: new (" Friedrich Nietzsche " , 3 , 4 ),
539
- Philosopher :: new (" Michel Foucault " , 0 , 4 ),
530
+ Philosopher :: new (" Plato " , 0 , 1 ),
531
+ Philosopher :: new (" Hypatia " , 1 , 2 ),
532
+ Philosopher :: new (" Lao " , 2 , 3 ),
533
+ Philosopher :: new (" Simone " , 3 , 4 ),
534
+ Philosopher :: new (" Audre " , 0 , 4 ),
540
535
];
541
536
542
537
let handles : Vec <_ > = philosophers . into_iter (). map (| p | {
@@ -572,9 +567,9 @@ struct Philosopher {
572
567
```
573
568
574
569
We need to add two more fields to our ` Philosopher ` . Each philosopher is going
575
- to have two forks : the one on their left, and the one on their right.
570
+ to have two chopsticks : the one on their left, and the one on their right.
576
571
We’ll use the ` usize ` type to indicate them, as it’s the type that you index
577
- vectors with. These two values will be the indexes into the ` forks ` our ` Table `
572
+ vectors with. These two values will be the indexes into the ` chopsticks ` our ` Table `
578
573
has.
579
574
580
575
``` rust,ignore
@@ -592,8 +587,8 @@ We now need to construct those `left` and `right` values, so we add them to
592
587
593
588
``` rust,ignore
594
589
fn eat(&self, table: &Table) {
595
- let _left = table.forks [self.left].lock().unwrap();
596
- let _right = table.forks [self.right].lock().unwrap();
590
+ let _left = table.chopsticks [self.left].lock().unwrap();
591
+ let _right = table.chopsticks [self.right].lock().unwrap();
597
592
598
593
println!("{} is eating.", self.name);
599
594
@@ -604,8 +599,8 @@ fn eat(&self, table: &Table) {
604
599
```
605
600
606
601
We have two new lines. We’ve also added an argument, ` table ` . We access the
607
- ` Table ` ’s list of forks , and then use ` self.left ` and ` self.right ` to access
608
- the fork at that particular index. That gives us access to the ` Mutex ` at that
602
+ ` Table ` ’s list of chopsticks , and then use ` self.left ` and ` self.right ` to access
603
+ the chopstick at that particular index. That gives us access to the ` Mutex ` at that
609
604
index, and we call ` lock() ` on it. If the mutex is currently being accessed by
610
605
someone else, we’ll block until it becomes available.
611
606
@@ -626,7 +621,7 @@ What about releasing the lock? Well, that will happen when `_left` and
626
621
` _right ` go out of scope, automatically.
627
622
628
623
``` rust,ignore
629
- let table = Arc::new(Table { forks : vec![
624
+ let table = Arc::new(Table { chopsticks : vec![
630
625
Mutex::new(()),
631
626
Mutex::new(()),
632
627
Mutex::new(()),
@@ -643,11 +638,11 @@ count will go up, and when each thread ends, it will go back down.
643
638
644
639
``` rust,ignore
645
640
let philosophers = vec![
646
- Philosopher::new("Baruch Spinoza ", 0, 1),
647
- Philosopher::new("Gilles Deleuze ", 1, 2),
648
- Philosopher::new("Karl Marx ", 2, 3),
649
- Philosopher::new("Friedrich Nietzsche ", 3, 4),
650
- Philosopher::new("Michel Foucault ", 0, 4),
641
+ Philosopher::new("Plato ", 0, 1),
642
+ Philosopher::new("Hypatia ", 1, 2),
643
+ Philosopher::new("Lao ", 2, 3),
644
+ Philosopher::new("Simone ", 3, 4),
645
+ Philosopher::new("Audre ", 0, 4),
651
646
];
652
647
```
653
648
@@ -678,16 +673,16 @@ With this, our program works! Only two philosophers can eat at any one time,
678
673
and so you’ll get some output like this:
679
674
680
675
``` text
681
- Gilles Deleuze is eating.
682
- Friedrich Nietzsche is eating.
683
- Friedrich Nietzsche is done eating.
684
- Gilles Deleuze is done eating.
685
- Baruch Spinoza is eating.
686
- Karl Marx is eating.
687
- Baruch Spinoza is done eating.
688
- Michel Foucault is eating.
689
- Karl Marx is done eating.
690
- Michel Foucault is done eating.
676
+ Hypatia is eating.
677
+ Simone is eating.
678
+ Simone is done eating.
679
+ Hypatia is done eating.
680
+ Plato is eating.
681
+ Lao is eating.
682
+ Plato is done eating.
683
+ Audre is eating.
684
+ Lao is done eating.
685
+ Audre is done eating.
691
686
```
692
687
693
688
Congrats! You’ve implemented a classic concurrency problem in Rust.
0 commit comments