1. Introduction
The goal of this paper is an in-depth investigation of Lamport’s Bakery algorithm (LBA) [
1] and some of its variants. LBA represents the first full solution to the mutual exclusion problem, stated by Dijkstra in 1965 [
2], for an arbitrary number N of processes. LBA is normally studied using informal arguments. However, proving properties of general concurrent/parallel systems by informal, mathematical reasoning is a well-known, difficult task. The source of difficulties rests on an intrinsic inability to predict and master all the process actions interleaving, which is dominated by nondeterminism. Model checking (MC) [
3,
4,
5] is a widely recognized important technique of automated reasoning, based on formal modelling of a system, which can be used to detect properties of a concurrent system. MC, though, can be limited, in the practical case, by the amount of data and the degree of nondeterminism in the state transition system of the model, which covers all the possible state execution paths of the model. In the cases where MC can be prohibitive to use, the techniques of statistical model checking [
6] (SMC) based on stochasticity and simulations can be used to estimate properties of a system model.
In this paper, LBA is first formally modelled using timed automata (TA) [
7] in the context of the Uppaal toolbox [
8,
9]. Since LBA generates unbounded tickets, the TA model is adapted for it to be verified by the statistical model checker of Uppaal [
9]. Moreover, to overcome the scalability problems of SMC, a reduction of a Uppaal model onto the efficient Theatre actor system [
10] is proposed and explored.
LBA is separately modelled and checked using atomic and non-atomic registers [
11,
12,
13,
14]. Atomic registers (AR) imply that read/write operations on the same register are indivisible. Non-atomic registers (NAR) allow read/write operations on the same register to occur simultaneously. The use of non-atomic registers is today very common due to the widespread diffusion of low-cost hardware devices (like phone cells) which depend on multi-port memories with relaxed control on the read/write operations. Proving properties of a mutual exclusion solution based on NAR is challenging. In fact, two basic problems to be handled exist [
12,
14]:
scrambling and
flickering. Scrambling refers to the case where two or multiple write operations execute concurrently on the same shared register. In this scenario, a nondeterministic value is actually written in the register. The flickering case occurs when one written operation gets executed concurrently with one or multiple read operations on the same register. In this situation, each reader process is returned a nondeterministic value belonging to the register type. Robustness against scrambling is very difficult to achieve. As a consequence, scrambling is often avoided by assuming that a variable is protected (fenced) by the adoption of low-level hardware mechanisms that deny concurrent writes on the register. Flickering is common in mutual exclusion algorithms (see later in this paper) where shared registers are assigned to distinct processes. This way, writing to a register is the responsibility of only one process, but all the registers can be consulted by all the other processes.
This paper confirms LBA is correct under both AR and NAR. In addition, fairness and the First-Come-First-Server (FCFS) property of processes are retrieved. Subsequently, the following variants of LBA based on bounded tickets are thoroughly investigated: (a) Taubenfeld’s Black-White [
15], which, similar to, e.g., Aravind’s Symmetric Tokens [
16], is correct only under atomic registers, (b) Sayyadabdi and Sharifi Bakery++ [
17], which emerges to be correct under both AR and NAR registers.
This paper significantly extends the preliminary work described in [
18], where a simplified timed model of LBA was developed and analyzed, without the FCFS property, through the Uppaal statistical model checker.
1.1. Background
In a mutual exclusion scenario, N ≥ two processes (or threads) compete for the use of a shared resource (e.g., a bank account during a financial transaction). For the overall concurrent system to be predictable, only one process at a time should be allowed to be in control of the shared resource. The code segment executed by a process that accesses/modifies the shared resource that is said to be the process’s critical section. Critical sections have to be regulated by a protocol, based, hopefully, on a minimal number of shared variables (registers), in such a way that critical sections are executed one after the other, i.e., sequentially. The general schema of a fault-free process is the following:
shared communication variables (registers)
Process(i), i being the process identifier in 1..N
local variables
repeat NCS; Enter-Code; CS; Exit-Code; forever
NCS is the non-critical section, that is, a code area where the process is not interested in the critical section (CS). A process starts competing for the critical section at the end of NCS. The Enter-Code represents the part of the protocol where a process declares its interest in the critical section. Such a part is often split into two sub-parts: the doorway and the waiting room. The doorway is a sequence of actions that the process executes to announce its willingness to enter the critical section. However, following the doorway, a process is forced to wait for the shared registers to change their values to permit the process to abandon the waiting room and enter the critical section. In the waiting room, a competing process executes one or more busy-waiting loops, where the process actively checks the shared registers until a condition eventually holds: await(condition); which is equivalent to while(!condition); The while equivalence clarifies that as long as the awaited condition is not met, the process actively cycles by doing nothing (the empty semicolon). The Exit-Code is the part of a mutual exclusion protocol, typically wait-free, where some registers are reset to their default values before going into the NCS. The duration of the NCS is arbitrary. An infinite duration mirrors process termination. The CS, instead, is assumed to be always fault-free and have a finite duration.
Besides the mutual exclusion (safety) property, a mutual exclusion protocol should ensure processes are deadlock-free (safety, no fatal mutual block of processes can be reached, that is, global progress is ensured) and starvation-free (a competing process eventually enters, after finite time, its critical section; that is, individual progress, is guaranteed).
1.2. Paper Organization
The paper is structured as follows.
Section 2 first introduces Lamport’s Bakery algorithm. Then, the formal modelling and verification approach based on Uppaal is clarified by presenting models of the algorithm based on atomic and non-atomic registers. The time-sensitive methodology is, then, extended to be more suited for statistical model checking. To improve model scalability, a formal model is reduced to actors in Java, which can be studied through simulations. Several experimental results are reported.
Section 3 applies the formal method and the actor programs to variants of the bakery algorithm that depend on bounded tickets. Such variants are fully evaluated using atomic and non-atomic registers. Finally, the conclusions are presented with an indication of ongoing and further work.
2. Modelling Lamport’s Bakery Algorithm
In the following, the formal method adopted for reasoning on mutual exclusion algorithms is clarified by showing a Uppaal model of Lamport’s bakery solution, whose pseudo-code is reported in Algorithm 1.
| Algorithm 1. Pseudo-code of Lamport’s Bakery Algorithm. |
shared communication variables: bool choosing[1..N]; //all false initially int number[1..N]; //tickets, all 0 initially Process(i): //ID i in 1..N local variables: int j; repeat
CS; //Exit-Code number[i] = 0; forever |
2.1. Operation of Lamport’s Bakery Algorithm
N processes, with identifiers in 1..N, interact with one another through the shared communication registers:
Each register is assigned to a distinct process, which is responsible for writing a value for it. However, all the registers can be checked by any other process. When process i is out of the critical section, it has its number[i] = 0. Processes compete for the critical sections by first acquiring a ticket, that is, the maximum of the number[] array, increased by 1. During the achievement of the ticket, choosing[i] is kept true. Regardless of the adopted memory model (atomic or non-atomic registers), number[] items are necessarily tested one at a time. The actions executed for obtainingthe ticket constitute the doorway section of the algorithm. Multiple processes can concurrently execute within the doorway. The doorway is followed by the waiting room section, where the process waits until it has the minimal ticket, which is the precondition for entering the critical section. The waiting room is composed of two busy-waiting loops, one for each partner process j in [1..N]. In a busy-waiting loop, the process actively checks some shared register(s) until a desired condition holds. First, process j is awaited until it (possibly) receives its ticket. Then, process j is awaited if it has priority with respect to the current process i, that is, it has a lower ticket. It is worth noting, in Algorithm 1, that multiple processes for nondeterminism, can obtain the same ticket. In this case, the priority comparison is based on the process identifiers. In particular, provided the partner process j is competing (number[j]! = 0), the lexicographic comparison order (number[j],j) < (number[i],i) is used, for ticket priority, in the second busy-waiting loop. This comparison is equivalent to
The correctness of Algorithm 1, that is, ensuring processes obey mutual exclusion, and are deadlock-free and starvation-free, is usually established by informal arguments. In the next section, a formal Uppaal model is proposed for property verification.
2.2. Uppaal Model Based on Atomic Registers
A Uppaal model for Lamport’s bakery algorithm working with atomic registers (AR) is shown in
Figure 1. Under AR, read/write operations on the same register are atomic or indivisible. In what follows, a concurrent action consists either of a read/write operation on a shared register or a group of operations on local variables. Local variables, in fact, can be held in cache registers and accessed without constraints. Since it is in a Uppaal model [
8], state evolution is ensured by executing one (instantaneous or timed) action at a time, and the decision is taken, in
Figure 1, to embed a basic action into the guarded command of the edge exiting from an urgent location. This way, concurrent actions are assumed to have a negligible duration (urgent location), and they can occur in any order (nondeterminism or partial order). A guarded command has a Boolean guard (true if omitted, green-colored in
Figure 1), a (possible) channel synchronization (azure-colored in
Figure 1), and an update part (blue-colored in
Figure 1), that is, an ordered sequence of variable assignments and clock resets. In the formal adopted method, process interactions are based on shared global variables (registers) only. The use of a broadcast and urgent channel synchronization (synch! in
Figure 1) is used to force exiting from a busy-waiting location (see below in this section).
The non-critical section is modelled by a normal location NCS, where the process can stay for an arbitrary time (also infinite, to express process termination). The critical section (CS) is instead modelled by a normal location with an invariant that ensures its duration is 1 time unit (TU). Another duration could be used as well. Each process is equipped with a local clock x. A clock can be reset (x = 0). After that, the clock grows automatically according to a standard rate to measure the time elapsed since its last reset. The use of timing in
Figure 1 is purposely related to the realization of busy-waiting loops. Whereas such a loop wastes instruction cycles in the physical core upon which the process (thread) is mapped for execution, in the Uppaal model, provisions are taken to minimize the redundant nondeterminism caused by busy-waiting loops. A busy-waiting loop is modelled through a normal location, where the process remains until the waited condition is met. Busy-waiting locations (see L7 and L9 in
Figure 1) are associated with a try condition (functions tryL7() and tryL9(), respectively, in
Figure 1). When the condition holds, the location is tentatively exited. Exiting is achieved by sending (!) a synchronization signal over the
broadcast and urgent channel. Such a synchronization is heard by no receiver and serves only the purpose of forcing the exit from the waiting location. It is worth noting that the urgent character of
makes the busy-waiting location, with a satisfied try function, behave as an urgent one (see L1, L2, and so forth, in
Figure 1), and thus, be affected by nondeterminism. A subtle aspect of try functions is that they can freely check one or multiple shared registers. As a consequence, the exit from a busy-waiting location is intended to be optimistic or tentative. It is the responsibility of the process to then check registers one-at-a-time non-deterministically. At any symptom, the busy-waiting was exited for an error, the waiting location is immediately re-entered.
The consequence of the above-described modelling design is that when a process enters its critical section (CS), all the remaining competing processes eventually reach a busy-waiting location, thus reducing the unnecessary nondeterminism. At the end of the critical section, the reset number[i] = 0 can cause another process to possibly gain its critical section and so forth.
From a model like
Figure 1, Uppaal builds the state graph (timed transition system) which can be navigated, by the model checker, with the help of a few Temporal Computational Tree Logic (TCTL) queries [
8], for property checking. Unfortunately, the LB-AR model of
Figure 1 is affected by state explosions in the state graph, because of the use of unbounded tickets. Model behavior can then be approximated by the statistical model checker [
9], which avoids building the state graph and uses simulations for property estimation.
2.3. Details of Model Declarations
Figure 1 refers to the behavior of generic process i (timed automaton). The following are the global declarations of the model:
const int N=8; //example
typedef int[1,N] pid; //subtype of process identifiers
urgent broadcast chan synch;
//shared communication variables
bool choosing[pid]; //all false initially by default
int number[pid]; //all 0 initially by default
const pid tp=1; //target process
int ov=−1, OV=−1;
int[0,1] cs=0;
void reset(const pid i){ if(i==tp) ov=0; }//reset
void update(const pid i){
if(i!=tp){
if(ov!=−1) ov=ov+1;
}
else{
if(ov>OV) OV=ov;
ov=−1;
}
}//update
A few auxiliary variables, , , and , are used to observe the maximum number of processes that simultaneously enter their critical section, and the maximum number () of competing processes that will bypass a given target process () waiting for entry into its critical section. The variable is incremented when a process is up to enter its critical section, and decremented on exiting from the critical section. As soon as would take a value greater than 1, a runtime error is signaled by SMC that stops its execution immediately. Therefore, the mutual exclusion property is implicitly assessed during any simulation query. The function will reset the variable when the target process starts competing. The function , invoked on exiting the critical section, will update the variable. should have a finite (hopefully minimal) value.
Each Process automaton has the sole parameter: const pid i, which specifies the process identifier. At the system configuration time, the statement:
will create N instances of Process, which are distinguished from one another by Process(1), Process(2), …, Process(N). Each Process instance introduces the following local variable declarations:
pid j;
int numi, numj;
clock x;
Variables numi and numj avoid recurrent access to shared registers. For example, following the doorway, numi holds the achieved ticket, which is also reflected in number[i]. This way, reading the number of a process j into numj will enable the local function cL11() to evaluate the lexicographic comparison order without accessing the shared registers. In Algorithm 2, all the Boolean functions used in the model LB-AR of
Figure 1 are detailed.
| Algorithm 2. Local Boolean functions of the Process model of Figure 1. |
bool tryL7(){ return !choosing[j]; }//tryL7 | bool tryL9(){ if(number[j]! = 0 && (number[j] < number[i] || (number[j] == number[i]) && j < i)) return false;
return true; }//tryL9 | bool cL11(){ return numj! = 0 && (numj < numi ||
(numj == numi) && j < i);}
//cL11 |
2.4. Model Adaptations for Statistical Model Checking
The model of
Figure 1 is not accepted by the Uppaal statistical model checker (SMC) [
9], because location NCS does not specify a time duration. A first workaround would be turning the NCS into an urgent location. This way, a process exiting from its critical section immediately re-enters the system and starts competing. Of course, this is a worst-case scenario, because it would induce maximal congestion. However, a better solution, which permits modelling some work effectively carried out during NCS, is portrayed in
Figure 2.
The new initial location is now Enter. From Enter, the process can reach NCSW (NCS working) with a probability weight w1, and NCSE (NCS end) with weight w2 (w1 + w2 = 100). After each time unit, a process in NCSW returns to Enter and, probabilistically, can go again to NCSW or reach NCSE, from which the normal behavior of
Figure 1 is reproduced. For simplicity, the elapsing of a time unit can be ensured by a Watch automaton like that in
Figure 3 (clock variable x is moved from Process to Watch). A further broadcast and urgent channel
are used. All the processes, either in CS or in the NCSW location, receive (?) the
signal to prosecute their behavior.
Some further variables and functions were added to the model of
Figure 2 for checking, particularly, the First-Come-First-Served (FCFS) property of doorway processes. In fact, Lamport’s bakery algorithm is expected to ensure that the group of competing processes that simultaneously execute the doorway, that is, in the same time frame, should gain the critical section
before any process of a subsequent doorway group. The following additional global variables were added:
tid cur=0; //current group
int now=0; //current time
bool fcfs=1; //optimism, 1=true, 0=false
bool dg[tid][pid]; //doorway groups
Current discrete time is held in the
variable. After each time unit (see the Watch model in
Figure 3), the inc() function increments
. Groups of doorway processes are collected, per time unit, in the bidimensional array dg[tid][pid]. When a process finishes its doorway, it is added to the current doorway group by the function add(i). Just before entering the critical section, the next unfinished doorway group is identified and becomes the current group (see the cdg() function in
Figure 2). It is worth noting that, due to the stochastic behavior following the Enter location, the next time could have an empty doorway group. The global Boolean
variable, initialized to true (or equivalent 1), will go to false (0) as soon as a process that has gained the right to enter the critical section is found, but does not belong to the current doorway group. The function checkFCFS(i) is responsible for controlling the value of the global
. Of course, for the FCFS property to be guaranteed, the variable
would always remain as true.
For completeness, the average number of critical sections executed by any process was also monitored.
2.5. Experimental Results
The Uppaal system consisting of Process and Watch automata of
Figure 2 and
Figure 3 were simulated on a Windows 11 Pro desktop platform with Dell XPS 8940, Intel i7-10700, CPU@2.90 GHz, 32 GB RAM, using Uppaal 5.0 (64 bit), and Java 25, for tEnd =
time units, for N ranging from 2 to 64 by powers of 2, with the following Metric Interval Temporal Language (MITL) queries [
9]:
simulate[<=100]{dg_size()}
simulate[<=10,000]{OV, fcfs}
simulate[<=10,000]{avg()}
Query 1 allows us to observe the size of doorway groups in the first 100 time units.
Figure 4 depicts the doorway group size dg_size() vs. time, when N = 8 processes are considered. As one can see, effectively, there are time frames where the doorway group is empty.
Figure 5 shows the average number of critical sections executed per process vs. time, always for N = 8 processes. After
time units, each process has executed, on average, about 1250 critical sections.
Figure 6 shows the values of
, and
vs. time for N = 8.
Table 1 collects the monitored values of
and
by using query 2. The last column of
Table 1 also reports the average number of critical sections executed by any process at the end of the simulation. Query 2 terminates after about 3 s when N = 32.
From the experimental runs, it emerged that the mutual exclusion is always satisfied (never is there an attempt to assign a value to greater than 1). In addition, the overtaking factor () and, in particular, was found to be bounded. The FCFS property was always observed, too. As a consequence, Lamport’s bakery algorithm was confirmed to be fair. If is the size of a doorway group, any process in the group will wait for at most processes before entering its critical section.
2.6. Lamport’s Bakery Model Based on Non-Atomic Registers
Shared registers of Lamport’s bakery algorithm are
output variables [
19]. Each register is assigned to a specific process that can write a value to it. All the registers, though, can be inspected by all the processes. In this Single-Writer-Multiple-Readers (SWMR) scenario, only the
flickering phenomenon can occur when non-atomic registers are used [
14,
20]. As a consequence, reading a register when no write is concurrently executing will return the last value assigned to the register. A read operation that occurs simultaneously with a write operation will, instead, return a nondeterministic value belonging to the type of the register. In addition, multiple readers can achieve different nondeterministic values when they co-occur with a write operation. As discussed in [
14], flickering can be modeled; in some cases, by decorating the model based on atomic registers with Boolean variables that are true when a write is in progress, or is otherwise false. In the proposed approach (see also [
14]), it is the responsibility of the reader process to check if a write is in progress and to possibly generate a nondeterministic value to use. The following decoration variables were introduced to adapt Lamport’s AR model to work with non-atomic registers (LB-NAR model):
//decoration variables
bool wc[pid], wn[pid];
wc[i] is assigned the value true when process i is assigning a value to its choosing[i] register; this is similar for wn[i], which is turned to true when the number[i] register is being assigned by process i. In the LB-NAR model, writing to a shared register is accomplished in two steps (atomic actions): the first one assigns a value to the register and sets true to the corresponding writing decoration variable. In the second step, the decoration value is reset to false. Due to model nondeterminism, it is perfectly possible that one or multiple readers make the attempt to read the register just after the first step but before the second one.
Figure 7 shows the derived LB-NAR model of Lamport’s bakery algorithm.
As one can see from
Figure 7, putting true (or 1) to choosing[i] when process i starts competing is split between the exit edges of locations L1 and L1a. In a similar way, when number[i] is set with the achieved ticket, the writing operation is split between the locations L5 and L5a, and so forth. A subtle point in
Figure 7 is the definition of the
type of a ticket register, in a situation where tickets are potentially unbounded. Considering that the LB-NAR model is necessarily studied in simulation using the Uppaal statistical model checker (SMC), some preliminary runs of the LB-AR model (see
Figure 2) were devoted to estimating the maximum value of tickets separately for each number N of the processes (e.g., up to N = 32), with a simulation lasting
time units. Such a maximum value was introduced in the LB-NAR model of
Figure 7 by a constant M, together with the following global subtype declarations:
The LB-NAR model of
Figure 7 relies on the nondeterministic selection (yellow-colored), which can be exploited in an edge guarded command. For example, in the edge from L3 to L4, if number[j] is not under writing (wn[j] is false), the local variable numj is assigned the actual value found in the register number[j]. Otherwise, a nondeterministic value v chosen in the
type is used instead. It is rather evident that the LB-NAR model exhibits a greater level of nondeterminism with respect to the LB-AR model in
Figure 2. All of this complicates the SMC analysis.
Figure 8 reports the observed doorway group size vs. time, when the LB-NAR model is simulated for 100 time units and N = 32 processes are used.
Figure 9 shows the estimation of the overtaking bound
and of the
property of the same system.
In all the experimental cases, the variable (which counts the number of processes that simultaneously enter their critical section) was always found to be contained in the range [0,1], thus confirming the fundamental mutual exclusion property.
The same results as those reported in
Table 1 for the LB-AR model were observed in the LB-NAR model about the average number of critical sections executed by each process at the end of
time units (confirming the expectation of a value about tEnd/N). However, due to the increased, although instantaneous in virtual time, nondeterminism, the case of N = 32 is completed by LB-NAR in 460 s of real time, rather than the approximately 9 s required by the LB-AR model.
In light of the accomplished experiments, it emerged that Lamport’s algorithm remains fully correct, as in the atomic register scenarios, and also when non-atomic registers are used. It is guaranteed that and that doorway processes are always handled according to the First-Come-First-Served strategy.
2.7. Improving Model Scalability by Actor Simulations
Lamport’s bakery LB-AR or LB-NAR model cannot be studied, with the Uppaal statistical model checker [
9], for large simulation times without incurring in declaration problems of the data structures used to check the doorway groups or having some integer variables (e.g., the
variable) which are affected by overflow (basic int data type is 16 bit with maximum integer being 32,767). To avoid these problems and to enable greater efficiency in model simulations, an Uppaal model is proposed to be reduced in actors in Java on top of the Theatre actor system [
10] by using some simple transformation rules.
Theatre actors are naturally time-sensitive, light-weight (that is, they are not thread-based), and can be regulated, transparently, by an application-dependent control machine (e.g., simulation). The asynchronous exchange of a message among actors represents the unit of scheduling and dispatching of the control machine. An actor is defined by its hidden data status and its message interface. Responding to a message is programmed in a message server method (introduced by the annotation @Msgsrv), which is the only means to modify the actor’s internal data status. Sending a message is accomplished by the fundamental send operation exposed by the Actor base class:
The (optional) attribute is a relative amount of time (measured from the current time ), after which the message has to be dispatched to its receiver actor. When omitted, evaluates to 0, meaning the message has to be delivered at the current time. The message name coincides with the name (string) of a message server method of the receiver. The Simulation control machine ensures that messages scheduled to occur at the same time get dispatched in an arbitrary order.
A Uppaal model is reduced in Theatre actors by introducing a Process actor, a Monitor actor, a Globals class containing all the necessary global declarations, and a model class that provides the Java main() method. The main() creates and initializes actors, instantiates the control machine, launches the execution (simulation), and finally collects the results that emerge.
Process actor directly corresponds to the Process Uppaal automaton. Each location is mapped to a distinct message server. The action associated with the exit of the location is programmed in the body of the message server method. In addition, the passage from one location to another is imitated by the process actor that sends the message corresponding to the next location to itself. To give a taste of the Process actor corresponding to Lamport’s LB-AR model (atomic registers), Algorithm 3 shows some local declarations and some message server methods of the first part of the LB-AR model (see
Figure 2).
The Monitor actor is responsible for the handling of the broadcast synchronizations related to the
and the
channels used in
Figure 2. Toward this, the monitor is informed by a process when it enters a busy-waiting location (message “busyWaiting”, which also carries the identity of the requesting actor) or it modifies (message “change”) some shared registers.
| Algorithm 3. A fragment of the Process actor in Java, corresponding to the LB-AR model of Figure 2 based on atomic registers. |
public class Process extends Actor{
private int i; //process identifier in 1..N
private int j; //in 1..N
private int numi, numj;
private Monitor monitor;
public enum Try{ Undef, TryL7, TryL9 };
public Try tryFunction = Try.Undef;
private enum Loc{ Undef, NCSW, CS };
private Loc curLoc = Loc.Undef;
…
@Msgsrv public void init(final Integer i, Monitor monitor) {
this.i = i; this.monitor = monitor; send(“Enter”);
}//init
@Msgsrv private void Enter() {
if(Math.random()<w1) send(“NCSW”);
else send(“NCSE”);
}//Enter
@Msgsrv private void NCSW() {
curLoc = Loc.NCSW; monitor.send(“timeWaiting”,this);
}//NCSW
@Msgsrv private void NCSE() {
reset(i); send(“L1”);
}//NCSE
@Msgsrv private void L1() { choosing[i] = 1; monitor.send(“change”); send(“L2”);
}//L1
@Msgsrv private void L2() {
j = 1; numi = 0; send(“L3”);
}//L2
…
}//Process |
The monitor is also informed when a process enters a location where the time unit advancement is awaited (message “timeWaiting” together with the identity of the actor). A list of the processes in busy-waiting and a list of the processes waiting for the time to advance are maintained. When the time arrives such that one or multiple actors are in the position to exit their busy-waiting (because the corresponding try functions evaluate to true), the monitor sends a “synch” message to all the involved actors. These actors are then removed from the waiting list. Monitor also has a private message server “tu” to reproduce the Watch actor in
Figure 3. Such a message is self-sent to be received after one time unit has elapsed. At each “tu”, the monitor sends a similar message to all the actors waiting for a time advancement. The corresponding list is then emptied. Each process actor introduces its version of the message server “synch” and of the message server “tu”. Message server “synch” re-checks the current try function and switches (that is, it makes a send operation) to the location that follows the busy-waiting one. Similarly, the “tu” message server reduces to self-sending a message corresponding to the location that has to be reached after the time unit advancement, that is, the “Enter” location in
Figure 2 and
Figure 7, following either the NCSW or the CS location. Before entering the target location, the action specified on the exit edge of the current location will be executed.
Algorithm 4 reports the main() method that configures and simulates the LB-AR actor program for an assigned time limit tEnd and a number N of processes, both defined in the Globals class.
| Algorithm 4. The main() method for the Lamport’s LB-AR actor program based on atomic registers. |
public class LB_AR {
public static void main(String[] args) {
long start = System.currentTimeMillis();
ControlMachine cm = new Simulation(tEnd);
initialize();
Monitor mon = new Monitor();
Process[] p = new Process[N + 1];
for(int i = 1; i <= N; ++i) p[i] = new Process();
mon.send(“init”);
for(int i = 1; i <= N; ++i) p[i].send(“init”,i,mon);
cm.controller(); //launches the simulation
long end = System.currentTimeMillis();
System.out.println(“OV = ”+OV);
System.out.println(“Avg_ncs x process = ”+(avg()));
System.out.println(“FCFS=” + fcfs); System.out.println(“ET=” + ((double)(end-start)/1000) + “sec”);
}//main
}//LB_AR |
Similarly, an LB-NAR actor program was derived when non-atomic registers are used. Different from the model in
Figure 7, an adaptive mechanism was adopted for the quantity M that represents the maximum value used to generate a nondeterministic ticket. The value of M is adjusted during the execution to always reflect the maximum ticket observed so far.
To give an idea of the performances of the LB-AR and LB-NAR actor programs,
Table 2 collects the results about the scenario of N = 512 processes with the simulation that lasts
time units. In no case was a mutual exclusion violation observed.
3. Bakery Algorithms with Bounded Tickets
Several algorithms are reported in the literature (see, e.g., [
15,
16,
17,
21,
22,
23]) which try to extend Lamport’s Bakery algorithm with techniques to bound the generated tickets. In the following, the Taubenfeld Black-White algorithm [
15] and the Sayyadabdi and Sharifi Bakery++ proposal [
17] were chosen as examples whose correctness are separately demonstrated with atomic and non-atomic registers.
3.1. Black-White Bakery Algorithm
The Black-White Bakery (BWB) algorithm extends Lamport’s Bakery (LB) algorithm by adding a color (black or white) to tickets. A bit is added to the shared variables. Tickets are couples <, >. For each color, ticket numbers range from to , where is the number of competing processes. When a process , , starts competing, it first reads the shared , which becomes the color of its ticket. The process then completes its ticket by evaluating the maximum number among the tickets having its same color. After that, process waits until its colored ticket is the lowest one (i.e., it has the highest priority). Then it enters its critical section.
The order among colored tickets is defined as follows. If two tickets have different colors, the process whose ticket has which is different from the shared value, has greater priority. In the case that the two tickets have the same color, the one with the smallest denotes the process with greater priority. Finally, if both tickets have the same and the same , the process with the smallest identifier has priority in entering the critical section.
The operation of the BWB algorithm is reported in Algorithm 5. On line 8, in the case the partner process j has the same color as process i, and process j is competing (ticket[j].number! = 0), the lexicographic order is used as in Lamport’s Bakery algorithm:
(ticket[i].number,i) < (ticket[j].number,j) ⇔
ticket[i].number < ticket[j].number V ticket[i].number == ticket[j].number && i < j
| Algorithm 5. Black-White Bakery algorithm. |
shared registers: color: a bit of type {black; white} choosing[1..N]: boolean array ticket[1..N]: (mycolor,number) array, with the elements of type {black,white} x [0..N]Initially: , choosing[j] = false, ticket[j].number = 0. The initialization of all the other variables is immaterial Process(i), i in {1..N} local variables: int j repeat 0. NCS 1. choosing[i] = true //doorway start 2. ticket[i].mycolor = color
3. ticket[i].number = 1 + max{ ticket[j].number, ticket[j].mycolor = ticket[i].mycolor }
4. choosing[i] = false //doorway end
5. for(j = 1 to N) do //waiting start
6. await choosing[j] == false
7. if(ticket[j].mycolor == ticket[i].mycolor) then
8. await ticket[j].number == 0 V ((ticket[i].number,i) < (ticket[j].number,j)) V ticket[j].mycolor! = ticket[i].mycolor
9. else
await ticket[j].number == 0 V (ticket[i].mycolor! = color) V (ticket[j].mycolor == ticket[i].mycolor)
10. fi od //waiting end
11. CS
12. if(ticket[i].mycolor == black) then color = white else color = black fi
13. ticket[i].number = 0 forever |
The last part of the await statement in line 8 checks if “ticket[j].mycolor! = ticket[i].mycolor”. In that case, in fact, process j has just started a new competition, and it certainly cannot have a higher priority than process i. Line 9 handles the case when process j and process i have different colors. Provided process j is competing (ticket[j].number! = 0), process i has greater priority than j if its color is different from the shared or, due to a prompt re-enter of process j, if the two processes exhibit the same color. In this situation too, process j certainly gained a greater number and then has a lower priority. After the exit from the critical section, the process i resets the global to the opposite value of its own . This way, process i gives priority to processes waiting that have the same color as process i. Finally, the of the process ticket is set to 0 to express its non-current interest in the critical section.
To summarize the BWB behavior, consider the first moves of the algorithm when the shared is assigned a value, e.g., white. This way, the first attributed tickets will all be white. The highest white-colored process enters its critical section, and at its exit, the process flips the shared to black. After that, the next assigned tickets will be black in color. Now we have white-colored and black-colored waiting processes. For the order relationship of different colored tickets, the next process to enter the critical section is necessarily white-colored. The situation will continue this way until all the previously white-colored processes have completed their critical section. Following this, the highest-priority black-colored process is allowed to enter its critical section and, at its end, it flips the shared to white. After that, the story repeats as before with all pending black-colored processes that gain permission to enter the critical section and so forth. The preceding discussion also preludes to the FCFS behavior. All the processes of a previous doorway group will enter the critical section before the processes of a subsequent doorway group.
3.1.1. Model Checking BWB
Since the use of bounded tickets, the BWB algorithm was expected to be amenable to property assessment by exhaustive model checking. First, the Uppaal model based on atomic registers (BWB-AR) (see
Figure 10) was derived from Algorithm 5.
The following global declarations were prepared to support colored tickets. A struct realizes the couple <mycolor, number>.
const int N = 3;
typedef int[1,N] pid;
typedef int[0,N] values;
const int black = 0, white = 1;
typedef int[black,white] bit;
typedef struct{
bit mycolor;
values number;
} val;
//shared communication registers
bit color;
bool choosing[pid];
val ticket[pid];
Global register initializations are ensured by an initialize() function, which gets invoked as the first model action through a bootstrapper automaton. Other global variables, such as
(target process),
and
(for the overtaking factor), and the bounded int[0,1]
(counter of the number of processes that simultaneously enter their critical section), are introduced as in Lamport’s LB-AR model of
Figure 1 (see
Section 2.3). Local declarations of each Process instance are the following:
pid j;
val tki, tkj;
clock x;
The two variables tki and tkj maintain, respectively, the ticket gained by the process (tki) and the ticket of a partner (tkj). Algorithm 6 shows the local try functions used in
Figure 10. The local boolean functions cL8a() and cL9a(), not reported for brevity, are identical to the tryL8() and tryL9() functions, except that they depend on the local variables tki and tkj instead of accessing the corresponding shared registers.
| Algorithm 6. Try-functions for exiting the busy-waiting locations in the model of Figure 10. |
bool tryL6(){
return !choosing[j]; }//tryL6 | bool tryL8(){
return ticket[j].number == 0 ||
(tki.number < ticket[j].number||
tki.number == ticket[j].number &&
i < j) || ticket[j].mycolor! = tki.mycolor;
}//tryL8 | bool tryL9(){
return ticket[j].number == 0 ||
tki.mycolor! = color ||
ticket[j].mycolor == tki.mycolor;
}//tryL9 |
Absence of deadlocks of the model in
Figure 10 was confirmed by satisfaction of the TCTL query [
8]:
that ensures no deadlocked state exists in the timed transition system (state graph) of the Uppaal model [
8]. The same query guarantees the mutual exclusion property because the model checker never terminated with the runtime error of trying to assign a value greater than 1 to the
variable.
Table 3 reports the overtaking bound (
) proposed by the model checker in the two cases: (a) the non-critical section (NCS location in
Figure 10) has an arbitrary duration (NCS is normal) or (b) it has a zero duration (NCS is urgent).
Table 3 also indicates the Elapsed Time (in seconds) and the peak of memory usage (M), of each execution of the TCTL query that checks the suprema value of
in every state of the state graph:
As one can see from
Table 3, with NCS normal, the model state graph explodes at N = 4 processes, whereas state explosions are registered at N = 5, in the case that NCS is urgent (maximal process congestion and absence of time uncertainty in NCS). In both cases, an
linear in the number of processes is confirmed. It is worth noting that for the experiments documented in
Table 3, no further variables were introduced to specifically check the FCFS property.
In order to permit a more complete investigation of the BWB-AR model, it was reduced to actors and simulated. The data structures required to detect doorway groups of processes (which relate to the same time frame) were introduced as in
Figure 2. The Boolean fcfs is expected to be always true if processes of the current doorway group are completely processed before the next group.
Table 4 collects the simulation results of the BWB-AR actor model for N ranging from 2 to 256 by powers of 2. Each simulation lasts
time units. The average number of critical sections (avg_ncs) executed by any process is also shown.
3.1.2. The BWB Model Under Non-Atomic Registers (BWB-NAR)
Similarly to Lamport’s Bakery algorithm, the BWB algorithm also rests on output variables. Each register, except for the shared
, is assigned to a separate process. On the other hand, the shared
can be changed by the only process that exits its critical section. As a consequence, only the flickering phenomenon has to be modeled. The BWB-AR model was then adapted to work with non-atomic registers (BWB-NAR) by using the decoration process described in
Section 2.6 (see also [
14]). Decoration variables were introduced to catch moments when the shared
register is under writing (cow), or a choosing variable is under writing (chw[i]), or a ticket is under writing, separately on its mycolor (tuw[i].myc) or its number (tuw[i].num) field. A maximum ticket, which, for flickering, evaluates to N, is not incremented.
The BWB-NAR model was then investigated by the Uppaal model checker. The following behavior emerged. BWB-NAR is correct for N = 2 processes, but for N > 2, the extra nondeterminism due to flickering makes the model lose the fundamental mutual exclusion property, as witnessed by the snapshot reported in
Figure 11. In
Figure 11, Process(1) is already in CS, and Process(3) is up to entering its CS because cL9a() is true due to tkj.number = 0 (j = 3). The increase in the cs variable then causes an out-of-range error that stops the model checker operation.
3.1.3. Discussion
The analysis of BWB’s algorithm confirmed the main results anticipated in [
15]. However, a new result emerged in this paper about the case of
processes, where BWB remains correct under atomic and non-atomic registers. For
, instead, BWB is incorrect under non-atomic registers. The correct BWB solution for
could then be exploited for building a general and correct solution for
, by using the solution for two processes as the arbitration unit in a tournament binary tree organization [
7,
14,
24]. This possibility will be investigated as future work.
For completeness, it is worth reporting that the formal modeling and verification approach advocated in this paper were also applied to Aravind’s Symmetric Tokens Bakery algorithm (STB) [
16]. STB, too, was found to be correct only with atomic registers, and with similar performance as BWB. Different from BWB, however, STB is incorrect when non-atomic registers are used, whatever the value
of the processes.
3.2. Bakery++ Algorithm
According to the intents of authors stated in [
17], Bakery++ (BPP) aims at bounding tickets of Lamport’s Bakery (LB) [
1] to avoid register overflows without introducing additional shared variables or redefining basic functions of LB. BPP rests on a constant
for tickets, which can never be exceeded. When a process
starts competing and finds another process has already achieved a ticket equal to
,
waits for the partner to exit its critical section and reset its ticket to 0. Due to nondeterminism, in the case that a process prosecutes with the attempt to get its ticket, and finds that a ticket equal to
was already released, the process ceases to choose its ticket, resets its number to 0, and comes back to the start of competition. The operation of BPP is illustrated in Algorithm 7.
| Algorithm 7. Behavior of Bakery++ (BPP) mutual exclusion algorithm. |
global declarations:
constant int N; //number of processes, with identifiers 1..N
constant int M; //maximum value for tickets
bool choosing[1..N] array, initialized to all false—as in Lamport’s Bakery
int number[] array, initialized to all 0—as in Lamport’s Bakery Process(i), i in {1,…,N} local declarations: int j repeat
NCS
L1. await for all j in {1,…,N} number[j] < M
choosing[i] = true
number[i] = max{ number[1], …, number[N] }
if(number[i] == M) then begin choosing[i] = false; number[i] = 0; goto L1 end
else number[i] = number[i] + 1 fi
choosing[i] = false
for(j = 1 to N) do begin
await choosing[j] == false
await number[j] == 0 or (number[i],i) < (number[j],j)
end od
CS
number[i] = 0 forever |
Figure 12 and
Figure 13 report two Uppaal models of BPP under atomic registers (BPP-AR) and non-atomic registers (BPP-NAR). Many operations and functions are the same as in Lamport’s Bakery models. The try functions tryL7() and tryL9() and logical function cL11() are identical to those shown in Algorithm 2. The tryL1() function is as follows:
3.2.1. Model Checking BPP-AR Model
According to its design, BPP requires a value for the constant M that avoids register overflows. When BPP is implemented in Java, a suitable value for M could be M = Integer.MAX_VALUE, which makes BPP, with atomic registers, behave as close as possible to Lamport’s Bakery (see also
Section 3.2.2).
For demonstration purposes, as observed in model checking, the BPP behavior was studied for when a smaller value for
is chosen, in particular,
. Absence of mutual exclusion violations and of deadlocks was assured by all the experiments carried out.
Table 5 reports the overtaking bound (OV) of the BPP-AR model, separately investigated by NCS normal and NCS urgent. It revealed an OV of about 2N − 2 when NCS is urgent, that is, it is abandoned in zero time.
3.2.2. Actor Simulations of BPP-AR and BPP-NAR Models
Due to the high degree of nondeterminism caused by flickering, the BPP-NAR model was found to be impossible to verify with the model checker, even for N = 2 processes. As a consequence, the experimental work was conducted by generating the actor programs corresponding to BPP-AR and BPP-NAR, and by simulating them in Java. The services of the Watch automata (see
Figure 3) were exploited, and NCS was split into the two locations, NCWS and NCSE, as in
Figure 2 and Algorithm 7, together with a probabilistic weight, w1 = 0.2, to favor starting a competition instead of NCS working.
Table 6 collects the estimated values of OV, the FCFS property, and the average number of executed critical sections of any process. Concerning the FCFS property, the BPP algorithm has an unclear doorway section. In fact, after starting a competition, a process can experiment with waiting due to partners who already gained M as a ticket. For the experiments, though, the doorway was assumed to be completed at location L6 in the
Figure 12 and
Figure 13. A process is added to the current doorway group on exiting from L6. Each simulation lasts
time units. The number N of processes was varied from 2 to 128 by powers of 2.
Table 6 confirms an estimation of OV
2N, even in the NAR scenario. It is worth noting that such a result complies with the FCFS property of doorway group processes. In fact, a process, before achieving its ticket (and entering the current doorway), can wait for processes having a ticket equal to M to reset it. Such a wait can encompass processes that re-enter, compete, and bypass a waiting process (e.g., the target process
used to estimate the OV).
For completeness, the BPP actor models were re-executed using M = Integer.MAX_VALUE = 2’147’483’647. Results are shown in
Table 7 and coincide with those of Lamport’s Bakery models, with an OV of about N − 1 and the FCFS, which is observed by doorway processes.
On the basis of the experimental results achieved, BPP emerged to be correct under both atomic and non-atomic registers.
4. Conclusions
This paper applied a formal framework based on Uppaal timed automata (TA) [
7] for modelling and analysis of bakery-based mutual exclusion algorithms. Both the Uppaal model checker (MC) with its exhaustive verification [
3,
8], and the statistical model checker (SMC) based on stochastic behavior and simulation [
6,
9] can be exploited. Moreover, to overcome the scalability problems of SMC, a reduction of an Uppaal model onto the efficient Theatre actor system in Java [
10] is defined, which enables a careful property analysis using simulations. The paper first models Lamport’s Bakery (LB) algorithm by retrieving, by simulations, its correctness under both atomic and non-atomic registers [
14]. LB, however, suffers from unbounded tickets, which cause register overflow in practical implementations. Two variants of LB are then considered: the Black-White Bakery algorithm (BWB) of Taubenfeld [
15], which is mainly correct when atomic registers are used, and the Sayyadabdi and Sharifi Bakery++ [
17] algorithm, which proves correct with atomic and non-atomic registers.
Prosecution of the research is geared to the following points. First, to experiment with the approach in other bakery-based algorithms (e.g., [
23]). Second, to generalize BWB, which emerged to be correct with atomic registers whatever the number
of processes, and with non-atomic registers only when
processes are involved. The idea is to embed it in a standard binary tournament tree [
7,
14,
24], where the solution for
processes is used as the arbitration unit at intermediate (including the root) competing nodes. The goal is to prove its correctness for
processes with either atomic or non-atomic registers.