Here we implement a different solution to the Dining Philosophers problem, described in "The Drinking Philosophers Problem", by K. M. Chandy and J. Misra [4]. Briefly, this algorithm efficiently and fairly solves the dining philosophers problem for philosophers connected in an arbitrary graph (as opposed to a simple ring). The algorithm works by augmenting each fork with a clean/dirty state. Initially, all forks are dirty. A philosopher is only obliged to relinquish a fork to its neighbor if the fork is dirty. On receiving a fork, the philosopher cleans it. On eating, the philosopher dirties all forks. For full details of the algorithm, consult the original paper.
Our implementation is based on the actor model of concurrency. An actor is a state machine which reacts to messages. On receiving a message, an actor can send asynchronous messages to other actors, change its state, or create new actors. Each actor is single-threaded and processes messages sequentially, which makes some concurrent programs easier to reason about and avoids explicit locking. Erlang is one popular language based on the actor model.
Orc emulates the actor model very naturally. In Orc, an actor is an Orc thread
of execution, together with a Channel
which serves as a mailbox. To send a
message to an actor, you place it in the actor's mailbox, and to receive a
message, the actor gets the next item from the mailbox. The internal states of
the actor are represented by functions: while an actor's thread of execution is
evaluating a function, it is considered to be in the corresponding state.
Because Orc implements tail-call optimization,
state transitions can be encoded as function calls without running out of stack
space.
In this program, a philosopher is implemented by an actor with three primary
states: eating
, thinking
, and hungry
.
An additional transient state, digesting
, is used to start a timer
which will trigger the state change from thinking
to
hungry
. Each state is implemented by a function which reads a
message from the mailbox, selects the appropriate action using pattern
matching, performs the action, and finally transitions to the next state
(possibly the same as the current state) by calling the corresponding function.
Forks are never represented explicitly. Instead each philosopher identifies a fork with the "address" (sending end of a mailbox) of the neighbor who shares the fork. Every message sent includes the sender's address. Therefore when a philosopher receives a request for a fork, it knows who requested it and therefore which fork to relinquish. Likewise when a philosopher receives a fork, it knows who sent it and therefore which fork was received.
[4] K. Mani Chandy and Jayadev Misra. 1984. The drinking philosophers problem. ACM Trans. Program. Lang. Syst. 6, 4 (October 1984), 632-646.