Skip to content

Commit a8e61dc

Browse files
committed
Dining philosophers
1 parent 3eeebe8 commit a8e61dc

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

README.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ is distributed under the [ISC license](LICENSE.md).
5454
- [Blocking transactions](#blocking-transactions)
5555
- [A transactional lock-free leftist heap](#a-transactional-lock-free-leftist-heap)
5656
- [Programming with transactional data structures](#programming-with-transactional-data-structures)
57+
- [The dining philosophers problem](#the-dining-philosophers-problem)
5758
- [A transactional LRU cache](#a-transactional-lru-cache)
5859
- [Programming with primitive operations](#programming-with-primitive-operations)
5960
- [Designing lock-free algorithms with k-CAS](#designing-lock-free-algorithms-with-k-cas)
@@ -743,6 +744,94 @@ first we need to `#require` the package and we'll also open it for convenience:
743744
# open Kcas_data
744745
```
745746

747+
#### The dining philosophers problem
748+
749+
The
750+
[dining philosophers problem](https://en.wikipedia.org/wiki/Dining_philosophers_problem)
751+
is a well known classic synchronization problem. It is easy to solve with
752+
**kcas**. If you are unfamiliar with the problem, please take a moment to read
753+
the description of the problem.
754+
755+
A handy concurrent data structure for solving the dining philosophers problem is
756+
the
757+
[`Mvar`](https://ocaml-multicore.github.io/kcas/doc/kcas_data/Kcas_data/Mvar/index.html)
758+
or synchronizing variable. A `'a Mvar.t` is basically like a `'a option Loc.t`
759+
with blocking semantics for both
760+
[`take`](https://ocaml-multicore.github.io/kcas/doc/kcas_data/Kcas_data/Mvar/index.html#val-take)
761+
and
762+
[`put`](https://ocaml-multicore.github.io/kcas/doc/kcas_data/Kcas_data/Mvar/index.html#val-put).
763+
For the dining philosophers problem, we can use `Mvar`s to store the forks.
764+
765+
The problem statement doesn't actually say when to stop. The gist of the
766+
problem, of course, is that no philosopher should starve. So, we'll make it so
767+
that we keep a record of how many times each philosopher has eaten. We'll then
768+
end the experiment as soon as each philosopher has eaten some minimum number of
769+
times. Programming a philosopher is now straightforward:
770+
771+
```ocaml
772+
# let philosopher ~fork_lhs ~fork_rhs ~eaten ~continue =
773+
let eat () =
774+
let take_forks ~xt =
775+
( Mvar.Xt.take ~xt fork_lhs,
776+
Mvar.Xt.take ~xt fork_rhs )
777+
in
778+
let (lhs, rhs) = Xt.commit { tx = take_forks } in
779+
780+
Loc.incr eaten;
781+
782+
let drop_forks () =
783+
Mvar.put fork_lhs lhs;
784+
Mvar.put fork_rhs rhs
785+
in
786+
drop_forks ()
787+
in
788+
789+
while continue () do
790+
eat ()
791+
done
792+
val philosopher :
793+
fork_lhs:'a Mvar.t ->
794+
fork_rhs:'b Mvar.t -> eaten:int Loc.t -> continue:(unit -> bool) -> unit =
795+
<fun>
796+
```
797+
798+
The dining philosophers main routine then creates the data structures and spawns
799+
the philosophers:
800+
801+
```ocaml
802+
# let dinining_philosophers ~philosophers ~min_rounds =
803+
assert (3 <= philosophers && 0 <= min_rounds);
804+
let eaten = Loc.make_array philosophers 0 in
805+
let continue () =
806+
eaten
807+
|> Array.exists @@ fun eaten ->
808+
Loc.get eaten < min_rounds
809+
in
810+
let forks =
811+
Array.init philosophers @@ fun i ->
812+
Mvar.create (Some i)
813+
in
814+
Array.iter Domain.join @@ Array.init philosophers @@ fun i ->
815+
Domain.spawn @@ fun () ->
816+
let fork_lhs = forks.(i)
817+
and fork_rhs = forks.((i + 1) mod philosophers)
818+
and eaten = eaten.(i) in
819+
philosopher ~fork_lhs ~fork_rhs ~eaten ~continue
820+
val dinining_philosophers : philosophers:int -> min_rounds:int -> unit =
821+
<fun>
822+
```
823+
824+
We can now run our solution and confirm that it terminates after each
825+
philosopher has eaten at least a given number of times:
826+
827+
```ocaml
828+
# dinining_philosophers ~philosophers:5 ~min_rounds:1_000
829+
- : unit = ()
830+
```
831+
832+
What makes dining philosophers so easy to solve with transactions is that we can
833+
simply compose two `take` operations to take both forks.
834+
746835
#### A transactional LRU cache
747836

748837
A LRU or least-recently-used cache is essentially a bounded association table.

0 commit comments

Comments
 (0)