Fact-checked by Grok 2 weeks ago

Dekker's algorithm

Dekker's algorithm is a software-based solution to the problem in concurrent programming, enabling two processes to safely access a without hardware support, by using only reads and writes to a small number of shared flags and an turn variable. It ensures that at most one process enters its at a time, while also guaranteeing progress (no ) and bounded waiting (no indefinite postponement). The algorithm was attributed to Dutch mathematician Th. J. Dekker by Edsger W. Dijkstra in his 1965 manuscript Cooperating sequential processes (EWD 123), marking it as the first known correct implementation for two processes relying solely on variables. Prior attempts by Dijkstra himself had flaws, but Dekker's version resolved them by introducing a turn variable to break symmetry when both processes simultaneously express interest in the . The core mechanism involves each process setting a to indicate its desire to enter the , then politely yielding if the other process's flag is set and the turn favors the opponent; upon exiting, the process resets its flag and updates the turn to the other. Dekker's algorithm laid foundational groundwork for subsequent solutions, including for two processes and Dijkstra's generalization to N processes, influencing modern concurrent programming despite its complexity for practical use today. Its proof of correctness relies on invariants ensuring that the flags and turn variable prevent simultaneous entry, with no assumptions about process speeds.

Background

Critical section problem

A critical section refers to a segment of code in a program where a process or thread accesses shared resources, such as variables, data structures, or files, that must be protected from concurrent modifications by other processes to prevent errors. In concurrent programming, where multiple processes execute simultaneously, the absence of synchronization mechanisms can lead to race conditions, in which the outcome of operations depends on the unpredictable interleaving of process executions, resulting in data inconsistency or corruption. For instance, if two processes attempt to increment a shared counter without coordination, one process might read the value, increment it locally, and write it back, only for the second process to overwrite the update with its own stale read, yielding an incorrect final value. The critical section problem is particularly pronounced in the two-process scenario, where two processes share a resource and alternate between non-critical sections (where they perform independent computations) and attempts to enter their critical sections. This setup assumes asynchronous execution, meaning processes proceed at arbitrary speeds without a global clock, and that reads and writes to shared variables are indivisible atomic operations, but no specialized hardware primitives (like test-and-set instructions) are available for synchronization. A key requirement for resolving this issue is mutual exclusion, ensuring that only one process enters its critical section at a time. In the , the emergence of multiprogramming systems—designed to maximize utilization of expensive computing by allowing multiple programs to reside in and share the CPU through —intensified these concurrency challenges, as processes could interleave unpredictably and corrupt shared data. This historical context motivated early researchers, including T. J. Dekker, to address the problem in software for two processes, laying the groundwork for solutions amid the growing complexity of systems like those developed at universities and labs in and the .

Requirements for solutions

Solutions to the mutual exclusion problem in concurrent programming must satisfy several essential properties to ensure correct and fair access to shared resources. and , originally articulated by in his analysis of cooperating sequential processes, form the foundation for algorithms like Dekker's. The primary property is , which guarantees that at most one process can execute in its critical section at any given time. This prevents race conditions where multiple processes simultaneously modify shared data, leading to inconsistent states. Without this, the integrity of the shared resource cannot be maintained. A second key requirement is , ensuring that if no process is currently in its critical section and at least one process wishes to enter, then the selection of the next process to enter cannot be postponed indefinitely. In other words, the system must avoid , where processes are unable to proceed despite no active critical section execution. This property demands that the decision process terminates in finite time, regardless of relative process speeds. Additionally, bounded waiting (also known as lockout freedom) requires that no is denied entry to its indefinitely; there must exist a bound on the number of times other es can enter the while a given is waiting. This prevents , where one is perpetually overtaken by others. These solutions operate under specific assumptions: exactly two es are involved, they communicate via variables with read and write operations, no assumptions are made about speeds or priorities, and execution in the non- is finite. Achieving these properties is non-trivial without hardware-supported operations beyond basic reads and writes, as interleavings of executions can lead to subtle failures in exclusion or if not carefully managed.

Algorithm Description

Shared variables

Dekker's algorithm relies on a small set of variables to enable for two concurrent processes without relying on specialized hardware instructions. The core variables include two flags, conventionally named flag[0] and flag[1], where each flag corresponds to one process and is set to true by that process to indicate its desire to enter the . Complementing the flags is a single integer variable turn, which can hold values or , serving to assign priority to one of the processes when both flags are simultaneously true. These variables are initialized with both flags set to false, signaling that neither process initially seeks access, and turn assigned an arbitrary value of 0 or 1. In operation, the flags primarily communicate intent, allowing processes to detect potential contention, while the turn variable resolves such conflicts by enforcing an order of service, thereby preventing and promoting progress. The correctness of the algorithm presupposes that all reads and writes to these shared variables are atomic operations, executed indivisibly relative to the other process.

Process protocols

Dekker's algorithm employs a symmetric for two processes, labeled Process 0 and Process 1, which use indices 0 and 1 to access shared variables flag[0], flag[1], and turn. Each process follows an identical structure but substitutes its own index for i and the opponent's as j = 1 - i, ensuring fairness through the interplay of flags indicating intent to enter the critical section and the turn variable resolving contention. The entry protocol begins with the process setting its own flag to true, signaling its desire to enter the . It then enters a loop checking the opponent's flag: if the opponent also wants entry (opponent's flag is true) and the turn favors the opponent, the process temporarily unsets its own flag to yield and waits in a inner loop until the turn changes to its favor before resetting its flag and retrying the check. This process repeats until the conditions allow entry, at which point the is accessed. The exit protocol is straightforward: the process unsets its own flag and sets the turn to favor the opponent, permitting the other process to proceed if interested. The full protocol for each process operates within an encompassing non-critical, entry, critical, and exit sections, as shown in the following for Process P_i where j = 1 - i:
do {
    // non-critical section

    flag[i] = true;
    while (flag[j]) {
        if (turn == j) {
            flag[i] = false;
            while (turn == j) {
                // busy wait
            };
            flag[i] = true;
        }
    }
    // [critical section](/page/Critical_section)

    turn = j;
    flag[i] = false;
    // non-critical section
} while (true);
Process 0 uses i = 0, j = 1; Process 1 uses i = 1, j = 0. The busy waits implement without additional primitives. Consider an example trace where Process 1 remains idle (flag = false throughout) and Process 0 attempts entry, assuming initial turn = 0. Process 0 executes its non-critical section, then sets flag = true. The while (flag) condition evaluates to false since flag is false, allowing immediate entry to the without looping or yielding. Upon exit, Process 0 sets turn = 1 and flag = false, completing the cycle and returning to non-critical execution. This demonstrates uncontested entry efficiency when one process is inactive.

Correctness Analysis

Mutual exclusion property

Dekker's algorithm ensures by guaranteeing that at most one can be in its at any time. The proof relies on analyzing the entry conditions of the processes based on the shared variables flag, flag, and turn. A P_i (where i is 0 or 1, and j = 1 - i) sets flag = true before attempting to enter its and executes a busy-wait loop: while (flag[j] && turn == j);. The loop exits if either flag is false (indicating P_j does not intend to enter) or turn == i (indicating it is P_i's turn). The then enters the while flag remains true, setting it to false only upon exit. Additionally, after exiting the , the sets turn = j to yield priority to the other . Consider the case where only one process intends to enter the critical section, say flag = true and flag = false. Process P_0 will exit its loop regardless of the value of turn, since ¬flag holds, allowing P_0 to enter alone. Process P_1 cannot enter because flag = false, so it does not attempt the loop. Similarly, if flag = true and flag = false, P_1 enters alone. In these non-conflicting cases, mutual exclusion is trivially satisfied, as only one process seeks access. In the conflict case where both flag = true and flag = true, the turn variable enforces strict alternation. Process P_0 exits its loop only if turn = 0, while P_1 exits only if turn = 1. Thus, depending on the current value of turn, exactly one process can proceed to its critical section, while the other remains in its loop. Upon exiting the critical section, the process sets turn to the value favoring the other, allowing it to eventually enter after the current occupant exits. This mechanism prevents simultaneous entry during conflicts. The algorithm maintains the invariant ¬flag ∨ turn = 0¬flag ∨ turn = 1 throughout execution, particularly when processes are attempting entry. This ensures that if flag = true, then turn = 0, and if flag = true, then turn = 1. When both flags are true, the invariant implies turn = 0turn = 1, which is impossible, preventing both processes from satisfying their entry conditions simultaneously. To formalize mutual exclusion, proceed by contradiction: assume both processes are simultaneously in their critical sections. At this point, flag = true and flag = true (since flags are cleared only after exit). For both to have entered, P_0's entry condition ¬(flag ∧ turn = 1) must hold, simplifying to turn = 0 (given flag = true). Similarly, P_1's condition requires turn = 1. This yields turn = 0turn = 1, a contradiction. Therefore, both cannot be in their critical sections at once. The invariant and loop conditions preserve this property across all executions. The possible states of the shared variables and corresponding entry permissions are summarized in the following table, illustrating that holds in all combinations (assuming processes have set their flags appropriately when attempting entry):
flagflagturnP0 can enterP1 can enter
truetrue0yesno
truetrue1noyes
truefalse0yesN/A
falsetrue1N/Ayes
This table demonstrates that when both flags are true, exactly one process can enter based on turn, enforcing exclusion.

Liveness properties

Dekker's algorithm satisfies the liveness properties of and bounded waiting, ensuring that processes neither nor starve when attempting to enter the . is guaranteed because if both processes wish to enter their critical sections, the turn variable assigns priority to one, allowing it to proceed, while the other yields; if only one process is interested, it enters immediately without . This dynamic priority mechanism, combined with finite loops and atomic read-write operations on shared , prevents indefinite postponement, as no exists where all interested processes are permanently blocked. The algorithm also ensures bounded waiting. Upon exiting the critical section, a process updates turn to favor the opponent, guaranteeing that a waiting process enters after the current occupant completes its turn. This alternation resolves contention fairly. Deadlock is avoided because the combination of flag variables and turn prevents mutual waiting states; if both flags are set indicating interest, the turn value explicitly favors one process, breaking any potential cycle. Earlier flawed algorithms, such as simple flag-based protocols without a tie-breaker, suffered deadlocks when both processes simultaneously signaled interest without resolution, a problem Dekker's design circumvents through its priority mechanism.

Historical Context

Origins and development

Dekker's algorithm originated in the late at the Mathematical Centre in , where Dutch mathematician Th. J. Dekker developed the first correct software solution to the problem for two processes. In 1959, , a colleague at the Centre, posed the challenge of synchronizing two cyclic processes to ensure only one accessed a shared at a time, amid growing interest in concurrent programming. Dekker provided a complete solution, including a proof of correctness, within three hours, marking a pivotal moment in early concurrency research. The algorithm emerged from practical challenges in multiprogramming on resource-constrained early computers, such as the Electrologica X1, which Dijkstra and his team used for implementing systems like the first compiler. These machines, with limited memory (e.g., 4K words on the X1), demanded efficient software mechanisms for process coordination without hardware support, driving innovations in shared-memory synchronization. Dekker's work remained an internal contribution at the Mathematical Centre until Dijkstra presented a solution to the problem in his 1965 Communications of the ACM paper, "Solution of a Problem in Concurrent Programming Control," and attributed the algorithm to Dekker in his 1965 manuscript Cooperating sequential processes (EWD 123). Prior to Dekker's breakthrough, several informal software attempts at had been explored in the nascent field of concurrent programming, but they were inherently flawed, often leading to deadlocks, , or violations of requirements. Dijkstra noted in his 1965 publication that although the problem had been articulated by various researchers, no prior solution fully met the criteria of , , and bounded waiting, underscoring the trial-and-error evolution that preceded a rigorous approach. This context of iterative refinement at the Mathematical Centre formalized Dekker's algorithm as a foundational in software-based .

Influence and legacy

Dekker's algorithm holds a pivotal place in the history of concurrent programming as the first proven correct software-only solution to the problem for two processes, relying solely on variables without hardware support. This breakthrough, attributed to Th. J. Dekker in Edsger W. Dijkstra's 1965 manuscript Cooperating sequential processes (EWD 123) and presented by Dijkstra in his contemporaneous Communications of the ACM paper, laid foundational groundwork for by demonstrating that could be achieved purely through software mechanisms, influencing the of reliable concurrent systems. The algorithm directly inspired subsequent innovations, notably Gary L. Peterson's 1981 algorithm, which simplifies Dekker's approach while retaining the use of flags and a turn variable to ensure and liveness for two processes. It also paved the way for extensions to multiple processes, such as Leslie Lamport's 1974 bakery algorithm, which generalizes the priority-based contention ideas from Dekker to n processes using numbers for fair access. In modern contexts, Dekker's algorithm serves as a cornerstone for lock-free programming techniques, where software-based avoids traditional locks to improve in multiprocessor environments. It remains a staple in operating systems textbooks for illustrating core concurrency principles and is frequently implemented in educational simulations to teach without hardware primitives. Despite its complexity relative to simpler variants like Peterson's, which has largely superseded it for practical two-process scenarios, Dekker's value endures in understanding software fundamentals, even as hardware instructions like dominate real-world applications. Ongoing analyses in concurrency literature, including formal verifications up to the 2020s, continue to explore and extend Dekker's ideas, confirming its enduring theoretical significance.

References

  1. [1]
    E.W.Dijkstra Archive: Cooperating sequential processes (EWD 123)
    In order to effectuate this mutual exclusion the two processes have access to a number of common variables. We postulate, that inspecting the present value ...
  2. [2]
    [PDF] Lecture Summary Mutual Exclusion
    This section presents two algorithms that solve the mutual exclusion problem. The first one is due to Dekker and it solves the mutual exclusion problem for two ...
  3. [3]
    [PDF] 5.4.1 Dekker's Algorithm - UCF Department of Computer Science
    Figure 5.8 Mutual exclusion implementation – version 3 (2 of 2). 5.4.1 Dekker's Algorithm. COP4600 : Operating Systems. Joohan Lee. 5.4.1 Dekker's Algorithm.
  4. [4]
    Operating Systems: Process Synchronization
    5.2 The Critical-Section Problem · Only one process in the group can be allowed to execute in their critical section at any one time. · The code preceding the ...
  5. [5]
  6. [6]
    [PDF] A Discipline of Multiprogramming: - UT Computer Science
    Additionally, concerns of concurrency are elim- inated from the model and introduced only during implementation. The programmers do not have to deal with ...
  7. [7]
    [PDF] threads-locks.pdf - cs.wisc.edu
    ASIDE: DEKKER'S AND PETERSON'S ALGORITHMS. In the 1960's, Dijkstra posed the concurrency problem to his friends, and one of them, a mathematician named ...
  8. [8]
    Concurrent Programming - University of Iowa
    Mutual exclusion can be assured even when there is no underlying mechanism such as the test-and-set instruction. This was first realized by T. J. Dekker and ...
  9. [9]
    [PDF] The Mutual Exclusion Problem Part II: Statement and Solutions
    These two properties, mutual exclusion and deadlock freedom, were the requirements for a mutual exclusion solution originally stated by Dijkstra in [2]. (Of ...
  10. [10]
    Proving Properties of Dekker's Algorithm for Mutual Exclusion of N ...
    Dekker's algorithm for mutual exclusion of two processes is the well-known first developed correct solution based only on software mechanisms.Missing: primary | Show results with:primary
  11. [11]
    [PDF] Sharing Objects Ch 3 Sharing Objects – Ch. 3 - eecs.wsu.edu
    techniques, Dekker's algorithm is intended to: techniques, Dekker s algorithm is intended to: 1. Provide mutual exclusion. 2. Avoid deadlock. 3. Avoid ...
  12. [12]
    [PDF] Solution of a Problem in Concurrent Programming Control
    The solution must satisfy the following requirements. (a) The solution must be symmetrical between the N computers; as a result we are not allowed to introduce ...
  13. [13]
    [PDF] Synchronization Tools
    while (true) 1 flag[i] = true; while (flag[j]) 1 if (turn == j) 1 flag[i] = false; while (turn == j). ; /* do nothing */ flag[i] = true; l l. /* critical ...
  14. [14]
    [PDF] Dijkstra Book Chapter on Concurrent Algorithms - Leslie Lamport
    May 14, 2025 · Dekker's and Dijkstra's algorithms, as well as many later mutual exclusion algorithms, are based on what I call the one-bit protocol: Each ...
  15. [15]
    [PDF] Intricacy of Concurrent Programming - UBC Computer Science
    Here we briefly review the algorithms of Dekker, Dijkstra, Knuth, Lamport, and Peterson. In Dekker's algorithm, a process accesses the shared resource straight ...
  16. [16]
    An Interview With Edsger W. Dijkstra - Communications of the ACM
    Aug 1, 2010 · This interview with programming pioneer Edsger Dijkstra (1930–2002) was conducted by CBI researcher Phil Frana at Dijkstra's home in Austin, TX, in August 2001.
  17. [17]
    Solution of a problem in concurrent programming control
    Solution of a problem in concurrent programming control. Author: E. W. Dijkstra ... Solution of a Problem in Concurrent Programming Control. Edsger Wybe Dijkstra.
  18. [18]
    [PDF] On the design of two small batch operating systems 1965 – 1970
    Nov 14, 2012 · This paper describes the design considerations and decisions for two small batch operating systems, called MICRO and MILLI, for the ...
  19. [19]
    [PDF] Mutual Exclusion: Classical Algorithms for Locks - Rice University
    “Mutual Exclusion.” Draft manuscript, 2005. • Gary Peterson. Myths about the Mutual Exclusion Problem. Information Processing Letters, 12(3), 115-116, 1981.
  20. [20]
    Contrasting Peterson's and Dekker's algorithms
    Jun 11, 2013 · Dekker's solution was the first mutual exclusion algorithm using just load and store instructions. It was introduced in Dijkstra, Edsger W.; " ...Missing: original | Show results with:original