In 1994, Trono proposed the Santa Claus Problem as an exercise in concurrent programming :
Santa Claus sleeps in his shop up at the North Pole, and can only be wakened by either all nine reindeer being back from their year long vacation on a tropical island, or by some elves who are having some difficulties making the toys. One elf’s problem is never serious enough to wake up Santa (otherwise, he may never get any sleep), so, the elves visit Santa in a group of three. When three elves are having their problems solved, any other elves wishing to visit Santa must wait for those elves to return. If Santa wakes up to find three elves waiting at his shop’s door, along with the last reindeer having come back from the tropics, Santa has decided that the elves can wait until after Christmas, because it is more important to get his sleigh ready as soon as possible. (It is assumed that the reindeer don’t want to leave the tropics, and therefore they stay there until the last possible moment.) The penalty for the last reindeer to arrive is that it must get Santa while the others wait in a warming hut before being harnessed to the sleigh.
Trono’s original solution uses ten semaphores. The problem is indeed intricate: as Ben-Ari argues, Trono’s solution assumes that a signalled process executes immediately: otherwise, when all reindeer are signalled to proceed to the sleigh, some reindeer may still not be harnessed while others have already finished delivering the toys . A more robust solution would need additional semaphores for barrier synchronization . Ben-Ari argues that the rendezvous construct of Ada is particularly suitable for this problem and compares a solution in Ada with one in Java using monitors. Downey proposes a solution of a simplified problem employing only four semaphores, but makes the assumption that a signalling process does not continue ; under some schedulers, e.g. the semaphore implementation of Python, the first elf runs forever.
The Santa Claus Problem follows a line of whimsically named concurrency problems (see  for a beautiful collection of those) that all are representative for specific aspects: here, these are priority (the reindeer have priority over elves), multi-party synchronization (all reindeer have to be present to engage with Santa and Santa engages either with reindeer or elves), barriers (all reindeer have to be harnessed, then they jointly ride with Santa, then Santa dismisses them), and batch processing (Santa consults elves one by one, but only if a group of three is present). The Santa Claus Problem has been used to illustrate concurrency constructs, e.g. [4, 7, 8, 9, 14] and for comparing concurrency constructs . Peyton Jones gives a solution in Haskell using software transactional memory . Welch and Pedersen present a process-oriented solution using Occam and discuss model-checking a CSP formulation of the problem .
This paper develops a solution using concurrent objects by a series of refinement steps. The thrust is to start the development with a specification that is as simple as possible, to add details about Santa, the reindeer, the elves, and their interaction in refinement steps, and to arrive at an implementation that is comparable to other efficient implementations. This work is part of an ongoing research program in developing a highly efficient implementation [13, 19] of concurrent objects together with an accompanying verification and refinement theory .
The next section introduces concurrent objects with guard-based synchronization and discusses the assumptions about atomicity. A general refinement rule for concurrent objects is given and informally justified. This is followed by the presentation of the Santa Claus problem, the development of a solution in five refinement steps, the timing results comparing four implementations, and a discussion. The proofs of the refinement steps are sketched but not carried out in full detail: our goal is to argue that the chosen model of concurrent objects allows both highly efficient implementations and intuitive correctness reasoning.
2 Concurrent Objects
Concurrent objects here consists of fields, methods, and actions [5, 6, 12, 16]. Methods must be called to execute but an action can execute on its own whenever its guard is true. Only one method or action can execute at a time in one object, but all objects can execute concurrently. Objects communicate through method calls; no separate mechanism is needed. For synchronization of objects, methods may also have a guard, which can block the caller. Consider class Santa:
When object st is created by st := new Santa, the method wakeup can be called, st.wakeup(). The call blocks if field of is not equal to Sleeping and sets to Working otherwise. The single action of the object is executed on its own when its guard is true, s Working, and then sets field to Sleeping. Thus this represents a Santa who needs to be woken up externally, but will go to sleep on his own.
The guards of methods and actions of an object can depend only on fields of that object; the guard cannot refer to fields of other objects or contain calls. This restriction is meant to allow for an efficient implementation: all objects can evaluate their guards concurrently without interference; a guard can change its value by execution with an object, hence guards only need to be reevaluated after a method or action in that object executes.
All methods and actions are executed atomically, up to method calls. For example, if is a statement without calls, the sequence st.wakeup() ; S ; st.wakeup() executes the first call st.wakeup() atomically, then atomically, then the second call st.wakeup() atomically. Using angular brackets to denote atomic regions, this is equivalent to st.wakeup() ; S ; st.wakeup() . Both calls to wakeup may block and delay execution until the guard holds, i.e. Santa is sleeping again. In general, if the execution of a method or action is suspended, another method or action may start to execute or continue execution. There can be arbitrarily many suspended method executions in an object. Once an action is chosen, that action will be executed until termination before another action can be initiated, hence at most one action execution can be suspended. There can only be as many concurrent executions as there are objects.
We assume that all fields of an object are private to the object, i.e. are accessed only by the methods and actions of the object. The sole purpose of classes is to create objects. In general, class refines class if a object can be used instead of a object. The following rule formalizes refinement with a coupling relation that relates the fields of and :
Rule 1 (Class Refinement)
Consider classes with list of fields, initialized to , with methods with bodies , and with actions . Class may have new methods with bodies :
Class is refined by class if for some relation over and :
or for all and some
for all and some
Condition () requires that the field initializations have to establish the coupling relation . Condition () requires that each method of refines the corresponding method of through . Condition () requires that new methods of either stutter, i.e. refine skip through , or refine some action of through . Condition () requires that all actions of refine some action of . Note that not all actions of have to be refined, i.e. can restrict the behaviour of .
For the refinement of statements through a relation, we give only a single rule and appeal to intuition otherwise:
Rule 2 (Guarded Assignment Refinement)
Let , be expressions over variables , let , be expressions over variables , and let be a relation between and :
In refinement steps, new classes may be introduced and objects of those classes may be created. Above rules are applied to ensure that the behaviour of existing objects is preserved.
3 Refining Santa
In the development below, subscripts are used to distinguish names across refinement steps.
Specification: Santa’s Cycle
The activity at the North Pole centers around Santa. In the simplest form, Santa either sleeps or works. This is expressed by a class with one field for Santa’s state and two actions that switch between these two states, whenever Santa feels like doing so:
A single object st of class Santa is created:
Refinement 1: Splitting Santa’s Work
Santa’s work consists of either delivering toys or helping the elves: when Santa wakes up, he may either go to state Delivering or Helping:
A single object st of class is created:
For applying the rule for Class Refinement, as the coupling relation between and we take:
Since there are no methods in , refinement follows from the conditions for the initialization and the four actions of :
Conditions () to () hold by the rule for Guarded Assignment Refinement.
Refinement 2: Introducing Santa’s Sleigh
Santa’s shop coordinates the elves and Santa’s sleigh coordinates the reindeer. We first introduce and prioritize the sleigh, postponing the introduction of the reindeer, the shop, and the elves. Here, the sleigh is an active object: the sleigh signals to Santa that all reindeer are back, then Santa harnesses all reindeer, then the reindeer pull the sleigh until Santa releases all reindeer and sleeps again. The synchronization is expressed by the sleigh calling newly introduced methods of Santa. Class splits the Delivering state of into Harnessing and Riding; field is true if the reindeer are back from vacationing:
Object of class and object of class are created; these objects can execute concurrently:
The first action of calls , which executes immediately but under mutual exclusion with any other action of . The calls and will block until the corresponding guard is true. For applying the rule for Class Refinement to show that refines , we take as the coupling relation:
Refinement follows as and of stutter under and refines the action of under . Field of reduces the nondeterminism that is present among the actions of . Formally, the conditions are:
These follow from the rule for Guarded Assignment Refinement.
Refinement 3: Introducing Reindeer
This step leaves Santa unchanged, refines Santa’s sleigh into a passive sleigh, and introduces active reindeer. The sleigh coordinates the reindeer by keeping a count, , for the number of reindeer that need to come back, that need to be harnessed, and that need to be pulling. The reindeer cyclically call the back, harness, pull methods of the sleigh. Since reindeer are not further refined, this is simply expressed by a single action composing these calls in sequence rather than by three actions.
One sleigh and nine reindeer are created:
As a note, the refinement is also correct is more or fewer than nine reindeer are created: if there are more than nine reindeer, the first nine arriving will be harnessed; if there are fewer than nine reindeer, Santa can only occupy himself with the Elves and no presents will be delivered! The coupling relation between Sleigh and Sleigh includes the identity relation on and restricts to be between and :
For brevity, we refer to the body of method of class as . Refinement of Sleigh by Sleigh then follows from:
To show , we distinguish the cases and : if initially, then simplifies to , which refines skip under ; if initially, then simplifies to , which refines under . The conditions and are shown similarly.
Refinement 4: Introducing Santa’s Shop
Class splits the Helping state of into Welcoming and Consulting; field is the number of puzzled elves. Santa will be woken up only by a group of three elves but then has to consult each individually. The shop is here an active object that represents the collective behaviour of elves: class maintains a count of the number of elves of the current group that still have to consult with Santa:
One Santa and one shop are created:
As the coupling relation between and we take:
Class refines as the methods back, harness, pull refine themselves under , new methods puzzled, enter stutter under , new method consult stutters if initially and refines if initially, and the two actions of refine actions of . Formally, the conditions are:
Refinement 5: Introducing Elves
This step leaves Santa, the sleigh, and the reindeer unchanged, refines the shop into a passive shop, and introduced active elves.
One shop and 20 elves are created:
The coupling relation between and includes the identity relation on . The count is also identical except in state Puzzled, as in the elves may increment one by one but in it is set to at once:
Class refines as the new method puzzled stutters under if and refines the action if , new method enter refines , and new method consult refines . Formally, the conditions are:
Summary of Refinement Steps
The final versions of classes Santa, Sleigh, Reindeer, Shop, and Elf are:
The main program creates active objects for Santa, reindeer, and elves; these use the passive sleigh and shop objects for synchronization:
We have implemented an experimental compiler for Lime, a language that closely follows the above theory of concurrent objects. Appendix A contains the Lime implementation of the Santa Claus Problem. The key contributions of the compiler are the management of dynamically growing stacks, the efficient evaluation of method and action guards, a mapping of actions to coroutines, and a distribution of coroutines onto processor cores. The details are in .
The Lime implementation is compared to implementations in C using semaphores of the Pthreads library, in Go using channels, and in Java using monitors, see Appendix A. Table 1 shows the running times for Santa with 9 reindeer and 20 elves. Santa’s division of work is that for 10,000 rounds until retirement, he rides the sleigh 2,000 times and helps 8,000 times groups of three elves, or for 20 elves, each elf on average 1,200 times. For 100,000 and 1,000,000 rounds until Santa’s retirement the ratio is the same. Some observations are in order:
|Repetitions of Santa||Lime (guards)||C (semaphores)||Go (channels)||Java (monitors)|
|10,000||0.04 / 0.04 / 0.00||0.87 / 0.26 / 1.18||0.08 / 0.12 / 0.01||6.38 / 2.48 / 5.30|
|100,000||0.30 / 0.30 / 0.00||8.82 / 2.50 / 12.0||0.77 / 1.18 / 0.06||60.3 / 21.6 / 52.0|
|1,000,000||2.91 / 2.90 / 0.01||93.0 / 24.8 / 123||7.51 / 11.6 / 0.55||534 / 159 / 509|
The Java implementation uses a single monitor for all synchronization. While it would be natural to have Santa, reindeer, and elf processes as well as sleigh, shop, and Santa monitors (synchronizing reindeer, elves, and the sleigh / shop, respectively), this leads to the nested monitor call problem, for example when elves are calling the shop and the shop calls Santa. Ben-Ari’s and our implementation use, therefore, a single monitor with the functionality of sleigh, shop, and Santa monitors. This limits concurrency, e.g. reindeer and elves cannot assemble independently. Java necessitates that each monitor method contains a notifyAll for waking up all threads, most of which will immediately sleep again. The timing results confirm that this is wasteful; in particular, the ratio between user and system times make the synchronization effort evident.
The C implementation uses operating systems threads, which require more cycles when switching than lightweight threads as used by Lime, Go, and Java. Compared to Java with monitors, only the “right” threads are woken up, but the ratio of user to system time tells that switching operating systems threads is expensive.
The Go implementation uses CSP-like synchronous channels, which are particularly suitable for barrier synchronization with Santa; by comparison, of the semaphore and operations, only one blocks, meaning that two semaphores are needed for each synchronization point. The goroutines (lightweight threads) of Go are mapped to coroutines, like in Lime, and distributed over cores (like in Lime), leading to good performance. Go does not support priorities when receiving or sending over channels, so to give reindeer priority over elves, a workaround is needed.
The Lime runtime system is designed for very quickly switching between actions when a guard blocks. Since the bodies of methods and actions in the Santa Claus Problem are short, this pays off. Interestingly, the real time is the user time, suggesting that only one core was active. The Lime runtime system is also designed for distributing a very large number of concurrent objects among cores. As there are relatively few objects here and the bodies of methods are so short that work stealing is not effective, the Lime runtime system is not able to utilize more than one core.
The Haskell implementation of Peyton Jones was not included as its proper functioning depends on the presence of delay statements. Trono’s implementation does not run reliably under Pthreads and has more relaxed synchronization constraints than the Lime version, so is not included in the comparison either.
In ongoing work, we observed on a number of concurrency examples, that Lime compares favourably to all other languages that we compared with , which made us wonder if that would be the case for the Santa Claus Problem as well. It took us by surprise that Lime is close to three times faster than Go, about 32 times faster than C, and more than 180 times faster than Java when measuring elapsed time. This line of work provides evidence that the evaluation of guards in methods and actions, compared to synchronizing with semaphores and monitors or sending over channels, is not intrinsically less efficient; the overall efficiency depends more on the techniques used for mapping actions to coroutines and quickly switching between them. This is encouraging for the use of verification and refinement techniques that rely on guards, as these can an applied to highly efficient implementations.
-  Gregory R. Andrews (1991): Concurrent Programming: Principles and Practice. Benjamin/Cummings Publishing Company.
-  Mordechai Ben-Ari (1998): How to solve the Santa Claus problem. Concurrency - Practice and Experience 10(6), pp. 485–496, doi:10.1002/(SICI)1096-9128(199805)10:6485::AID-CPE3293.0.CO;2-2.
-  Nick Benton (2003): Jingle bells: Solving the Santa Claus problem in Polyphonic C#. Technical Report, Microsoft Research. Available at https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/santa.pdf.
-  Marcello M. Bonsangue, Joost N. Kok & Kaisa Sere (1999): Developing Object-based Distributed Systems. In P. Ciancarini, A. Fantechi & R. Gorrieri, editors: 3rd IFIP International Conference on Formal Methods for Open Object-based Distributed Systems (FMOODS’99), Kluwer, pp. 19–34, doi:10.1007/978-0-387-35562-7_3.
-  Martin Büchi & Emil Sekerinski (2000): A Foundation for Refining Concurrent Objects. Fundamenta Informaticae 44(1,2), pp. 25–61. Available at http://content.iospress.com/articles/fundamenta-informaticae/fi44-1-2-02.
-  Peter A. Buhr (2016): High-Level Concurrency Constructs. In: Understanding Control Flow: Concurrent Programming Using C++, Springer International Publishing, Cham, pp. 425–522, doi:10.1007/978-3-319-25703-7_9.
-  Nick Cameron, Ferruccio Damiani, Sophia Drossopoulou, Elena Giachino & Paola Giannini (2006): Solving the Santa Claus problem using state classes. Technical Report, Dip. di inf., Univ. di Torino. Available at http://www.di.unito.it/~damiani/papers/scp.pdf.
-  Steingrim Dovland (2006): Liberating Coroutines: Combining Sequential and Parallel Execution. Master’s thesis, University of Oslo, Department of Informatics. Available at http://urn.nb.no/URN:NBN:no-11637.
-  Allen B. Downey (2016): Little Book of Semaphores. Green Tea Press. Available at http://greenteapress.com/semaphores.
-  Jason Hurt & Jan B. Pedersen (2008): Solving the Santa Claus Problem: a Comparison of Various Concurrent Programming Techniques. In Peter H. Welch, Susan Stepney, Fiona A.C. Polack, Frederick R.M. Barnes, Alistair A. McEwan & Adam T. Sampson Gardiner S. Stiles, Jan F. Broenink, editors: Communicating Process Architectures 2008, IOS Press, pp. 381–396, doi:10.3233/978-1-58603-907-3-381.
-  Jayadev Misra (2002): A Simple, Object-Based View of Multiprogramming. Formal Methods in System Design 20(1), pp. 23–45, doi:10.1023/A:1012904412467.
-  Joshua Moore-Oliva, Emil Sekerinski & Shucai Yao (2014): A Comparison of Scalable Multi-Threaded Stack Mechanisms. Technical Report CAS-14-07-ES, McMaster University, Department of Computing and Software. Available at http://www.cas.mcmaster.ca/cas/0reports/CAS-14-07-ES.pdf.
-  Piotr Nienaltowski (2007): Practical framework for contract-based concurrent object-oriented programming. Ph.D. thesis, ETH Zürich, doi:10.3929/ethz-a-005363875.
-  Simon Peyton Jones (2007): Beautiful concurrency. In A. Oram & G. Wilson, editors: Beautiful Code: Leading Programmers Explain How They Think, O’Reilly, pp. 385–406. Available at https://www.schoolofhaskell.com/school/advanced-haskell/beautiful-concurrency.
-  Emil Sekerinski (2005): Verification and Refinement with Fine-Grained Action-Based Concurrent Objects. Theoretical Computer Science 331(2–3), pp. 429–455, doi:10.1016/j.tcs.2004.09.024.
-  John A. Trono (1994): A new exercise in concurrency. ACM SIGCSE Bulletin 26(3), pp. 8–10, doi:10.1145/187387.187391.
-  Peter H. Welch & Jan B. Pedersen (2010): Santa Claus: Formal Analysis of a Process-oriented Solution. ACM Trans. Program. Lang. Syst. 32(4), pp. 14:1–14:37, doi:10.1145/1734206.1734211.
-  Shucai Yao (2018, Draft): An Efficient Implementation of Guard-based Synchronization for an Object-Oriented Programming Language. Ph.D. thesis, McMaster University.
These implementations are used in the comparison of timing results.