Not much time left till Xmas.. Haven’t you ever wondered how Santa is doing all his business with those elves and reindeer? And why?
Even if you don’t care, there are people who do. At least one. John Trono of St. Michael’s College in Vermont was so much worried about how Santa can handle all that stuff, that he decided to emulate his work and wrote quite a simplified scenario of Santa’s life:
The ninth reindeer signals Santa and then joins the others waiting for
reindeer_sem. When it’s signalled, they invoke
get_hitched.
The code for elves is quite similar, but it uses another turnstile for three-elves-stuff:
elf_mutex.wait()
mutex.wait()
elves += 1
if elves == 3
{
santa_sem.signal()
}
else
{
elf_mutex.signal()
}
mutex.signal()
elf_sem.wait()
get_help()
mutex.wait()
elves -= 1
if elves == 0
{
elf_mutex.signal()
}
mutex.signal()
The first two elves release
elf_mutex at the same time they release the mutex, but the last elf holds
elf_mutex, preventing other elves from entering until all three elves have invoked
get_help. The last elf to leave releases
elf_mutex, allowing the next batch of elves to enter.
The Ocaml part
Now the time come to have some fun with Ocaml. First thing to mention is that Ocaml does not have any semaphore built-in class-or-something (it’s because of it’s rich standard library). But it’s not a big issue since it has Mutex and Condition classes (yeah, Ocaml is an Objective Caml and it do has classes) in the
Threads library and we can use them to write our own semaphore. To make a semaphore more or less serious, let’s write it in the separate module.
module Semaphore = struct
class semaphore initial_count initial_name =
object (self)
val mutable count = initial_count
val name = initial_name
val sync = Mutex.create()
val cond = Condition.create()
method inc n = count <- count + n
method dec n = count <- count - n
method signal ?(n=1) () =
Mutex.lock sync;
self#inc n;
for i = 1 to n do
Condition.signal cond
done;
Mutex.unlock sync
method wait =
Mutex.lock sync;
while count = 0 do
Condition.wait cond sync
done;
self#dec 1;
Mutex.unlock sync
end
end;;
My semaphore has internal
mutable (yeah, Ocaml is not a pure functional language like Haskell is) field
count (used for a “gate width“ for threads to enter semaphore simultaneously), internal
name (used for logging, when I was looking for a deadlock in my code some time ago), one mutex and one condition variable. It has two primary methods:
signal with optional parameter N and
wait which are just usual increment-decrement/lock-unlock methods of semaphore:
signal increments internal counter and if it’s positive, it allows threads to enter critical section which given semaphore guards and
wait decrements counter and if it’s zero, calling thread is blocked until counter becomes positive.
If an Ocaml program is split into modules then you can expect a big fun trying to compile that program. First, you have to generate interfaces for that module. Secondly, you have to compile both interface and the module itself:
ocamlc -thread unix.cma threads.cma -i semaphore.ml > semaphore.mli
ocamlc -thread unix.cma threads.cma -c semaphore.mli
ocamlc -thread unix.cma threads.cma -c semaphore.ml
To enable multithreading support (in terms of Posix compatible threads) you have to compile your code with
-thread key and include unix and threads compiled modules.
Now let’s write main program. Since we’re dealing with logging and multithreading, I’ve written a function-helper, which uses our Semaphore class to synchronize printing to stdout:
let stdout_sem = new Semaphore.semaphore 1 "stdout_sem";;
let puts s =
stdout_sem#wait;
Printf.printf "%s\n" s;
flush stdout;
stdout_sem#signal ();;
Next, I’m using some kind of transport structure for Santa, reindeer and elves functions (shared between them and protected by semaphore). This structure (a record in terms of Ocaml) contains counters and semaphores as discussed earlier:
type santa_counters = { mutable elves : int;
mutable reindeer : int;
santa_sem : Semaphore.semaphore;
reindeer_sem : Semaphore.semaphore;
elf_sem : Semaphore.semaphore;
elf_mutex : Semaphore.semaphore;
mutex : Semaphore.semaphore };;
and a simple initializer:
let new_santa_counters () = { elves = 0;
reindeer = 0;
santa_sem = new
Semaphore.semaphore 0 "santa_sem";
reindeer_sem = new
Semaphore.semaphore 0 "reindeer_sem";
elf_sem = new
Semaphore.semaphore 0 "elf_sem";
elf_mutex = new
Semaphore.semaphore 1 "elf_mutex";
mutex = new
Semaphore.semaphore 1 "mutex" };;
To make our example more realistic I’ve implemented functions
prepare_sleigh and others to see what’s actually happens using my helper for synchronized printing:
let prepare_sleigh () = puts "Prepare sleigh";;
let help_elves () = puts "Help Elves";;
let get_hitched () = puts "Get Hitched";;
let get_help () = puts "Get Help";;
You might thought right now that braces
() in the end of each function are kind of usual braces like in Java, C++ etc, but actually it’s an argument of type unit of each function. Please, refer to the
tutorials for more details.
Let’s take a look on our pseudo-code solutions implemented in Ocaml:
let santa_role_func c =
c.santa_sem#wait;
c.mutex#wait;
if c.reindeer = 9 then (
prepare_sleigh ();
c.reindeer_sem#signal ~n:9 ();
c.reindeer <- 0;
)
else if c.elves = 3 then (
help_elves ();
c.elf_sem#signal ~n:3 ()
);
c.mutex#signal ();;
let reindeer_role_func (c, i) =
let s = Printf.sprintf
"Starting reindeer (%d)" i in
puts s;
c.mutex#wait;
c.reindeer <- c.reindeer + 1;
if c.reindeer = 9 then c.santa_sem#signal ();
c.mutex#signal ();
c.reindeer_sem#wait;
get_hitched ();;
let elves_role_func (c, i) =
let s = Printf.sprintf
"Starting elf [%d]" i in
puts s;
c.elf_mutex#wait;
c.mutex#wait;
c.elves <- c.elves + 1;
if c.elves = 3 then
c.santa_sem#signal ()
else
c.elf_mutex#signal ();
c.mutex#signal ();
c.elf_sem#wait;
get_help ();
c.mutex#wait;
c.elves <- c.elves - 1;
if c.elves = 0 then c.elf_mutex#signal ();
c.mutex#signal ();;
You can notice that
santa_role_func accepts one parameter
c (our transport structure), but two others accept two parameters. It’s because Santa’s role function is running in a loop and others are running just one time. Second parameter in elves and reindeer functions is an index of a thread in which they’re running (for debug and visualization purposes).
The last (except of compilation) step to implement is to make all this stuff work together:
let c = new_santa_counters () in
let santa_loop () =
puts "Starting Santa loop";
while true do
santa_role_func c;
done
in
let santa_array = [| Thread.create santa_loop () |]
and
reindeer_array = Array.init 9
(fun i -> Thread.create reindeer_role_func (c, i))
and
elf_array = Array.init 20
(fun i -> Thread.create elves_role_func (c, i))
in
Array.iter Thread.join
(Array.concat [santa_array; reindeer_array; elf_array]);;
Code above creates three arrays of threads:
santa_array (which always contains just one element),
reindeer_array (always contains 9 reindeer threads) and
elf_array (which contains 20 (fairly chosen) elves threads). After each thread is started, main program joins all of them using humble functional magic with
Array.Iter.
What had happened on the North Pole
I’ve copied typical stdout from a santa_problem solution below (and the ocaml version for the clarification).
> ocaml -version
The OCaml toplevel, version 4.01.0
> ./build.sh
> ./santa_problem
Starting santa loopStarting reindeer (4)
Starting reindeer (5)
Starting reindeer (6)
Starting reindeer (3)
Starting reindeer (7)
Starting reindeer (8)
Starting elf [0]
Starting reindeer (2)
Starting elf [1]
Starting elf [2]
Starting elf [3]
Starting elf [4]
Starting reindeer (1)
Starting elf [5]
Starting elf [6]
Starting elf [7]
Starting elf [8]
Starting elf [9]
Starting elf [10]
Starting elf [11]
Starting elf [12]
Starting reindeer (0)
Starting elf [13]
Starting elf [14]
Starting elf [15]
Starting elf [19]
Prepare sleigh
Starting elf [16]
Starting elf [18]
Get Hitched
Get Hitched
Get Hitched
Get Hitched
Get Hitched
Get Hitched
Get Hitched
Get Hitched
Get Hitched
Starting elf [17]
Help Elves
Get Help
Get Help
Get Help
Help Elves
Get Help
Get Help
Get Help
Help Elves
Get Help
Get Help
Get Help
Help Elves
Get Help
Get Help
Get Help
Help Elves
Get Help
Get Help
Get Help
Help Elves
Get Help
Get Help
Get Help
……
Merry Xmas
Santa’s problem is one of the classical synchronization problems and is worth looking into. A lot of solutions exist nowadays. For example, there is
an interesting approach to solve it in Haskell using Software Transactional Memory (STM). Despite the fact Ocaml does not provide us with as cool features as STM, we can see that building parallel programs in Ocaml is
easy fun! As far as I can see, the solution above is kind of first pure Ocaml solution of Santa’s problem.
You can download all code from the
Santa's gist.