Requirements for Solutions to the Critical Section Problem
Correctness Proofs: Rigorous Verification
Read aloud is unavailable in this browser.
The Need for Mathematical Certainty
Throughout this module, we have evaluated synchronization algorithms by reasoning about their properties. But reasoning alone leaves room for error. Subtle bugs can hide in informal arguments. The history of computer science is filled with algorithms believed correct for years before subtle flaws were discovered.
This brings us to the gold standard of verification: formal correctness proofs. A formal proof provides mathematical certainty that an algorithm satisfies its requirements under stated assumptions. No testing can provide this level of assurance—only proof.
This page introduces the techniques of formal proof for synchronization algorithms. We will cover:
- The mathematical foundations of correctness proofs
- Invariant-based proof techniques
- Structural induction for safety properties
- Variant-based proofs for liveness properties
- Complete proofs of classical algorithms
By the end, you will understand how to construct and verify rigorous proofs of synchronization correctness.
By the end of this page, you will understand the structure of formal correctness proofs, master invariant-based proof techniques for mutual exclusion, apply variant-based proofs for progress and bounded waiting, and be able to construct complete correctness proofs for synchronization algorithms.
Foundations of Formal Proofs
Formal proofs of concurrent algorithms differ from sequential program proofs. The non-determinism introduced by arbitrary scheduling requires techniques that account for all possible interleavings.
The Concurrent Program Model
We model a concurrent program as a transition system:
States (S): The set of all possible configurations of shared and local variables.
Initial States (S₀ ⊆ S): The starting configurations.
Transitions (→ ⊆ S × S): The set of atomic steps that take the system from one state to another.
A program execution is a sequence of states s₀ → s₁ → s₂ → ... where s₀ ∈ S₀ and each sᵢ → sᵢ₊₁ is a valid transition.
Safety vs. Liveness Properties
Properties of concurrent systems divide into two categories:
Safety Properties: "Nothing bad ever happens"
- Mutual exclusion is a safety property
- Formally: If the property is violated, it's violated at some finite point
- Proved by showing an invariant that implies the safety condition
Liveness Properties: "Something good eventually happens"
- Progress and bounded waiting are liveness properties
- Formally: If the property is violated, we observe infinite waiting
- Proved using fairness assumptions and variant functions
| Property Type | Intuition | Examples | Proof Technique |
|---|---|---|---|
| Safety | Nothing bad happens | Mutual exclusion, deadlock-freedom | Invariants |
| Liveness | Something good happens | Progress, bounded waiting, termination | Variants + fairness |
Fairness Assumptions
Liveness properties require fairness assumptions about the scheduler. Without them, a scheduler could indefinitely delay any process, violating any liveness property trivially.
Weak Fairness: If a process is continuously enabled (can take a step), it eventually takes a step.
Strong Fairness: If a process is infinitely often enabled (can take a step infinitely often), it eventually takes a step.
Most liveness proofs assume at least weak fairness. Without fairness, we can only prove safety properties.
Consider a spinlock: a process spins in 'while(locked){}'. With weak fairness, if the lock is eventually released, the process will eventually see it and proceed. Without fairness, the scheduler could always schedule other processes, and the spinner never gets a chance to check.
Invariant-Based Proofs for Safety
The primary technique for proving safety properties is invariant-based reasoning. An invariant is a predicate over states that, once established, remains true throughout execution.
Inductive Invariants
An invariant I is inductive if:
-
Initialization:
Iholds in all initial states∀s ∈ S₀: I(s) -
Preservation:
Iis preserved by all transitions∀s, s': (I(s) ∧ s → s') → I(s')
If I is inductive, then I holds in all reachable states. This follows by induction on the length of executions.
Using Invariants to Prove Mutual Exclusion
To prove mutual exclusion:
-
Define an invariant
Ithat:- Is inductive (holds initially, preserved by all transitions)
- Implies mutual exclusion:
I(s) → at_most_one_in_cs(s)
-
Since
Iholds in all reachable states, mutual exclusion holds in all reachable states.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
// Structure of an Invariant-Based Proof // Goal: Prove mutual exclusion for algorithm A // STEP 1: Define the invariant I// I must be strong enough to imply mutual exclusion// I must be preserved by every atomic action in the algorithm Example invariant for a lock-based algorithm: I ≡ (in_cs[i] → lock == HELD_BY_i) for all i // STEP 2: Prove initialization// Show I holds in the initial stateProof: Initially, no process is in CS. in_cs[i] = false for all i. Therefore, (in_cs[i] → lock == HELD_BY_i) is vacuously true. ∴ I holds initially. ✓ // STEP 3: Prove preservation// For each atomic action a, show: I(s) ∧ s →ᵃ s' → I(s') Action: Process i executes "acquire lock" Precondition: I(s), lock is free Effect: lock = HELD_BY_i Goal: Show I(s') Proof: No process was in CS (since lock was free and I(s)). After: if i enters CS, in_cs[i] = true, lock = HELD_BY_i. (in_cs[i] → lock == HELD_BY_i) holds. ✓ Action: Process i executes "release lock" Precondition: I(s), in_cs[i] = true Effect: lock = FREE, in_cs[i] = false Goal: Show I(s') Proof: After, in_cs[i] = false. (false → lock == HELD_BY_i) is true. ✓ [Continue for all actions...] // STEP 4: Conclude mutual exclusion// Show I → MUTEX where MUTEX ≡ ¬(in_cs[i] ∧ in_cs[j]) for i ≠ j Proof: Suppose in_cs[i] ∧ in_cs[j] for some i ≠ j. By I: lock == HELD_BY_i (since in_cs[i]) By I: lock == HELD_BY_j (since in_cs[j]) But HELD_BY_i ≠ HELD_BY_j, contradiction. ∴ ¬(in_cs[i] ∧ in_cs[j]). MUTEX holds. ✓Strengthening the Invariant
Sometimes the obvious invariant isn't strong enough to be inductive. We may need to strengthen it—add more conjuncts that help preservation even if they're not directly needed for the final property.
For example, if we need to prove P, and P alone isn't inductive, we find Q such that:
P ∧ Qis inductiveP ∧ Q → P(trivially true)
Finding the right strengthening is an art. Common techniques:
- Include conditions linking control locations to variable values
- Include conditions about what must be true when a process is at certain points
- Include global properties that constrain the combined state
When a simple invariant fails to be preserved by some transition, ask: 'What additional condition would make it preserved?' The answer often reveals the strengthening needed. Invariant discovery is iterative—propose, test preservation, refine.
Complete Proof: Peterson's Algorithm Mutual Exclusion
Let us construct a complete, rigorous proof that Peterson's algorithm satisfies mutual exclusion.
The Algorithm (Formalized)
12345678910111213141516171819202122
// Peterson's Algorithm - Formal Specification // Shared Variables:// flag[0], flag[1] : Boolean (initially false)// turn : {0, 1} (initially 0) // Local Variables for process i:// pc[i] : {idle, want, wait, cs, exit} (program counter) // Process i (for i ∈ {0, 1}, let j = 1 - i): // Location transitions:// idle → want: flag[i] := true// want → wait: turn := j// wait → cs: (flag[j] = false) ∨ (turn = i) [guard]// cs → exit: <critical section complete>// exit → idle: flag[i] := false // Initial State:// pc[0] = idle, pc[1] = idle// flag[0] = false, flag[1] = false// turn = 0The Invariant
We propose the following invariant:
I ≡ ∀i ∈ {0,1}: (pc[i] ∈ {wait, cs, exit} → flag[i] = true)
∧
∀i ∈ {0,1}: (pc[i] = cs → (flag[j] = false ∨ turn = i))
The first conjunct says: if a process is waiting, in CS, or exiting, its flag is set.
The second conjunct says: if a process is in CS, either the other's flag is false or it's this process's turn.
Proof of Initialization
123456789101112131415161718192021222324
// Proof: I holds in the initial state Initial state: pc[0] = idle, pc[1] = idle flag[0] = false, flag[1] = false turn = 0 First conjunct for i = 0: pc[0] ∈ {wait, cs, exit}? No (pc[0] = idle) Therefore, (pc[0] ∈ {wait, cs, exit} → flag[0] = true) is vacuously true. ✓ First conjunct for i = 1: pc[1] ∈ {wait, cs, exit}? No (pc[1] = idle) Vacuously true. ✓ Second conjunct for i = 0: pc[0] = cs? No (pc[0] = idle) Vacuously true. ✓ Second conjunct for i = 1: pc[1] = cs? No (pc[1] = idle) Vacuously true. ✓ ∴ I holds in the initial state. ✓Proof of Preservation
We must show that each transition preserves I. There are 5 transitions per process; we analyze the critical ones:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
// Proof: each transition preserves I // TRANSITION: idle → want (for process i)// Effect: flag[i] := true, pc[i] := want Before: Assume I holds.After: - First conjunct for i: pc[i] = want ∉ {wait, cs, exit}, so vacuously true. ✓ - First conjunct for j: unchanged (we only modified process i's state). ✓ - Second conjuncts: pc[i] ≠ cs, pc[j] unchanged. If pc[j] was not cs, still not cs. If pc[j] = cs, then by I before, (flag[i] = false ∨ turn = j). But we just set flag[i] = true! Does this break I? Hmm, potential issue. Let's check: If pc[j] = cs before, we had (flag[i] = false ∨ turn = j). After, flag[i] = true, so we need turn = j to maintain. But wait—we only set OUR flag. Turn was set during j's want→wait. Since j is in cs, j already passed the guard, so turn = j at that time. Turn only changes in want→wait transitions. Since we're in idle→want, we don't change turn. ∴ turn = j still holds. ✓ // TRANSITION: want → wait (for process i)// Effect: turn := j, pc[i] := wait Before: Assume I holds.After: - First conjunct for i: pc[i] = wait, flag[i] was set in idle→want, and we haven't cleared it. But wait, prove that flag[i] = true. Issue: We need flag[i] = true when pc[i] = wait. Strengthen I: Add "pc[i] ∈ {want, wait, cs, exit} → flag[i] = true" (extend the set to include 'want') OK, assuming strengthened I: Before, pc[i] = want, so flag[i] = true. We don't modify flag[i] in this transition. After, pc[i] = wait, flag[i] = true. ✓ - First conjunct for j: unchanged. ✓ - Second conjunct for i: pc[i] = wait ≠ cs, vacuously true. ✓ - Second conjunct for j: If pc[j] = cs, need (flag[i] = false ∨ turn = i). We just set turn = j, so turn ≠ i (since i ≠ j). We need flag[i] = false. But flag[i] = true! This seems like a problem... but wait: If pc[j] = cs, then j passed the guard: flag[i] = false ∨ turn = j. Since we're now setting turn = j, that's consistent. After this transition, turn = j, so (turn = j) is true. ∴ (flag[i] = false ∨ turn = j) holds. But the invariant for j says (flag[i] = false ∨ turn = j). After, turn = j, so this holds. ✓ // TRANSITION: wait → cs (for process i)// Guard: (flag[j] = false) ∨ (turn = i)// Effect: pc[i] := cs Before: Assume I holds, and guard is true.After: - First conjunct for i: pc[i] = cs, flag[i] was true (strengthened I). ✓ - Second conjunct for i: pc[i] = cs, need (flag[j] = false ∨ turn = i). But this was exactly the guard! We only take this transition when the guard is true. ∴ The condition holds. ✓ // TRANSITION: exit → idle (for process i) // Effect: flag[i] := false, pc[i] := idle Before: Assume I holds.After: - First conjunct for i: pc[i] = idle ∉ {want, wait, cs, exit}. Vacuous. ✓ - We set flag[i] = false. Does this affect second conjunct for j? If pc[j] = cs, we need (flag[i] = false ∨ turn = j). After, flag[i] = false, so this holds! ✓ ∴ All transitions preserve I. ✓Proof that I Implies Mutual Exclusion
1234567891011121314151617181920212223242526272829
// Proof: I → MUTEX // MUTEX ≡ ¬(pc[0] = cs ∧ pc[1] = cs) Proof by contradiction: Assume I holds and pc[0] = cs ∧ pc[1] = cs. By second conjunct of I applied to process 0: pc[0] = cs → (flag[1] = false ∨ turn = 0) ∴ flag[1] = false ∨ turn = 0 ... (1) By second conjunct of I applied to process 1: pc[1] = cs → (flag[0] = false ∨ turn = 1) ∴ flag[0] = false ∨ turn = 1 ... (2) By first conjunct of I applied to process 0: pc[0] = cs → flag[0] = true ∴ flag[0] = true ... (3) By first conjunct of I applied to process 1: pc[1] = cs → flag[1] = true ∴ flag[1] = true ... (4) From (1) and (4): Since flag[1] = true, we must have turn = 0. From (2) and (3): Since flag[0] = true, we must have turn = 1. But turn cannot be both 0 and 1. Contradiction! ∴ ¬(pc[0] = cs ∧ pc[1] = cs). MUTEX holds. ∎We have rigorously proven that Peterson's algorithm satisfies mutual exclusion. The key insight is that the turn variable creates an asymmetry: both processes cannot have 'their turn' simultaneously, so both cannot satisfy the entry guard at the same time.
Variant-Based Proofs for Liveness
Liveness properties assert that something good eventually happens. We cannot prove such properties using invariants alone—invariants only constrain what CAN happen, not what WILL happen.
The Variant Method
To prove liveness, we use variant functions (also called ranking functions or progress measures):
A variant is a function V: States → W where:
Wis a well-founded set (no infinite descending chains)- Progress: each step toward the goal decreases
V - Bounded:
Vcannot decrease forever
Common choices for W:
- Natural numbers (with usual ordering)
- Tuples of naturals (lexicographic ordering)
- Finite ordinals
Proving Progress
To prove progress (if CS is empty and processes contend, one enters):
- Define a variant measuring 'distance' from entry
- Show under fairness, the variant decreases
- Since the variant can't decrease forever, entry occurs
12345678910111213141516171819202122232425262728293031323334353637383940414243
// Structure of a Variant-Based Liveness Proof // Goal: Prove that waiting processes eventually enter CS // STEP 1: Define the progress condition// C(s) ≡ "processes are waiting but CS is empty" // STEP 2: Define the variant V// V: States → ℕ (or some well-founded set)// V measures "how far" we are from a process entering CS Example for Peterson's: V(s) = (2 if both in wait, 1 if one in wait and one not, 0 otherwise) But this is too coarse. Better: V(s) = < number of processes ahead in "queue", number of steps until guard satisfied > // STEP 3: Show V decreases while C holds// Under fairness, show that eventually:// Either C becomes false (a process enters - goal achieved!)// Or V decreases Proof sketch: Case: Both processes in wait state, turn = j. Then process i with turn = i will satisfy guard (flag[j] ∨ turn = i). Since turn = j (for j), process j is blocked. But process i's guard (flag[j] ∨ turn = i) evaluates to (true ∨ false) = true. Wait, let me reconsider... If turn = j, then: - Process i's guard: flag[j] = true (j is waiting), turn = i = (1-j) ≠ j. So flag[j] = true and turn ≠ i. Guard is false for i. - Process j's guard: flag[i] = true (i is waiting), turn = j. So turn = j. Guard (flag[i]=false ∨ turn=j) becomes (false ∨ true) = true! So process j enters. V decreases (one fewer waiting). ✓ // STEP 4: Conclude// Since W is well-founded and V decreases while C holds,// eventually C becomes false (some process in CS).// ∴ Progress holds.Proving Bounded Waiting
Bounded waiting requires a stronger argument: not just that SOME process enters, but that EACH waiting process enters within bounded bypasses.
We use a variant that tracks the specific waiting process:
Vᵢ(s) = "distance for process i to enter CS"
And show that:
- Each bypass by other processes decreases Vᵢ (or maintains it)
- The bound is computable from the initial Vᵢ
Liveness proofs fundamentally require fairness assumptions. The variant decreases when the 'right' process takes a step. Fairness guarantees that the 'right' process eventually takes its step. Without fairness, we cannot conclude that the variant ever decreases.
Complete Proof: Peterson's Progress and Bounded Waiting
Let us complete the correctness proof for Peterson's algorithm by proving progress and bounded waiting.
Proof of Progress
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
// Proof: Peterson's algorithm satisfies Progress // Goal: If CS is empty and at least one process is in entry (want or wait),// eventually some process enters CS. // Assumption: Weak fairness (continuously enabled → eventually executes) // Case 1: One process (say i) is in entry, the other (j) is in remainder. Process i is at want or wait. j is in remainder (idle state), so flag[j] = false. If i is at want: Under fairness, i eventually executes want → wait (sets turn = j). Now i is at wait. i is at wait: Guard: flag[j] = false ∨ turn = i Since flag[j] = false (j in remainder), guard is TRUE. Under fairness, i eventually executes wait → cs. ∴ Process i enters CS. Progress satisfied. ✓ // Case 2: Both processes are in entry. Both at want or wait states. Sub-case 2a: At least one is at want. Under fairness, it executes want → wait. Eventually, both are at wait. Sub-case 2b: Both at wait. flag[0] = true, flag[1] = true (by invariant). turn = 0 or turn = 1. If turn = 0: Process 0's guard: flag[1] = true, turn = 0. Guard = (true ∨ true) = true... wait, that's wrong. Guard = (flag[1] = false) ∨ (turn = 0) = false ∨ true = true. ✓ Process 0 can proceed. Under fairness, process 0 enters CS. If turn = 1: By symmetric argument, process 1 enters CS. ∴ Some process enters CS. Progress satisfied. ✓ // Conclusion: Under weak fairness, Peterson's algorithm has Progress. ∎Proof of Bounded Waiting
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
// Proof: Peterson's algorithm satisfies Bounded Waiting with B = 1 // Goal: After process i enters its entry section, process j can enter// CS at most once before process i enters. // Assume process i enters entry section at time t₀ (sets flag[i] = true). // Case 1: Process j is not in entry/CS at time t₀. j is in remainder (idle), so flag[j] = false. As shown in progress proof, i will enter CS. j bypasses i 0 times. ✓ // Case 2: Process j is in CS at time t₀. j will eventually exit (finite CS execution assumption). When j exits: - j sets turn = i (in exit section) - j sets flag[j] = false Now, if j wants to enter again: j sets flag[j] = true j sets turn = i (in want → wait) j arrives at wait with turn = i In the meantime, i is in entry (want or wait). If i is at wait: turn = i (set by j's exit or j's entry!) i's guard: flag[j] = true, turn = i Guard = (true ∨ true) = true... no wait, Guard = (flag[j] = false) ∨ (turn = i) = false ∨ true = TRUE. ✓ i can enter. If i is at want: Under fairness, i moves to wait. i sets turn = j. But then j's exit will set turn = i. Eventually i sees turn = i and enters. Key insight: After j exits CS and before i enters CS, if j sets turn again (in want→wait), it sets turn = i. This guarantees i's guard becomes true. j entered CS once (was already in at t₀), i then enters. j bypasses i at most 1 time. ✓ // Case 3: Process j is in entry at time t₀. j is at want or wait. turn is either 0 or 1. If turn = j (i.e., it's j's turn): j can enter (guard satisfied). j enters CS (bypass 1). j exits, sets turn = i. Now i's guard: (flag[j] = false) ∨ (turn = i). After j's exit, flag[j] = false, so guard is true. i enters. j bypassed i once. ✓ If turn = i: j's guard: flag[i] = true, turn = i ≠ j. Guard false. j cannot enter. i's guard: flag[j] = true, turn = i. Guard true. i enters. j bypassed i zero times. ✓ // Conclusion: In all cases, j enters at most once before i.// Bounded Waiting holds with B = 1. ∎We have now rigorously proven that Peterson's algorithm satisfies all three requirements: Mutual Exclusion (via invariant proof), Progress (via case analysis with fairness), and Bounded Waiting with B=1 (via bypass counting). Peterson's algorithm is correct!
Summary of Proof Techniques
Let us consolidate the proof techniques we have developed:
Technique Taxonomy
| Property | Type | Technique | Key Elements |
|---|---|---|---|
| Mutual Exclusion | Safety | Invariant | Find I such that I is inductive and I → MUTEX |
| Absence of Deadlock | Safety | Invariant | Show ¬(all waiting ∧ none can proceed) |
| Progress | Liveness | Variant + Fairness | Show variant decreases until entry |
| Bounded Waiting | Liveness | Bypass Counting | Count max bypasses per waiting process |
| Termination | Liveness | Variant | Show execution eventually completes |
Common Invariant Patterns
Certain invariant patterns recur across synchronization algorithms:
- •Location → Variable:
at(L) → var = val(e.g., in CS implies lock held) - •Gate condition:
in_cs[i] → gate_condition[i]where gate prevents simultaneous entry - •Turn exclusivity:
turn has one value at a time, so two different turn values cannot hold - •Flag semantics:
flag[i] = true ↔ process i is interested - •Conservation:
Σ in_cs[i] + Σ waiting[i] + Σ idle[i] = n(all processes accounted for)
Common Proof Pitfalls
| Pitfall | Description | Remedy |
|---|---|---|
| Weak invariant | Invariant doesn't imply property | Strengthen with additional conjuncts |
| Non-inductive | Invariant not preserved by all transitions | Strengthen or relax invariant |
| Missed transition | Forgot to check some program action | Systematically enumerate all actions |
| Conflated atomicity | Treated non-atomic as atomic | Decompose into true atomic steps |
| Omitted fairness | Proved liveness without fairness | State fairness assumption explicitly |
| Case explosion | Didn't use structure to simplify | Use symmetry and case merging |
- Start with the property to prove. 2. Guess an invariant (for safety) or variant (for liveness). 3. Try to prove initialization and preservation. 4. When a step fails, analyze why and strengthen. 5. Iterate until complete. 6. Verify the invariant implies the desired property.
Mechanized Proofs and Verification Tools
While hand-written proofs are valuable for understanding, they are error-prone. Mechanized proofs use computer assistance to verify correctness with mathematical certainty.
Interactive Theorem Provers
Provers like Coq, Isabelle/HOL, and Lean allow humans to construct proofs interactively, with the computer verifying each step:
- Human provides proof strategy and key insights
- Computer checks all logical steps rigorously
- Final proof is machine-verified
Model Checkers (State-Space Exploration)
Tools like SPIN, TLA+, and UPPAAL exhaustively explore the state space:
- Automatically check all reachable states
- Report counterexamples if property fails
- Limited by state explosion, but highly automated
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
--------------------------- MODULE Peterson ---------------------------EXTENDS Integers VARIABLES flag, turn, pc vars == <<flag, turn, pc>> Init == /\ flag = [i \in {0, 1} |-> FALSE] /\ turn \in {0, 1} /\ pc = [i \in {0, 1} |-> "idle"] (* Process i transitions *)Enter(i) == /\ pc[i] = "idle" /\ pc' = [pc EXCEPT ![i] = "want"] /\ flag' = [flag EXCEPT ![i] = TRUE] /\ UNCHANGED turn SetTurn(i) == LET j == 1 - i IN /\ pc[i] = "want" /\ pc' = [pc EXCEPT ![i] = "wait"] /\ turn' = j /\ UNCHANGED flag EnterCS(i) == LET j == 1 - i IN /\ pc[i] = "wait" /\ (flag[j] = FALSE \/ turn = i) /\ pc' = [pc EXCEPT ![i] = "cs"] /\ UNCHANGED <<flag, turn>> ExitCS(i) == LET j == 1 - i IN /\ pc[i] = "cs" /\ pc' = [pc EXCEPT ![i] = "idle"] /\ flag' = [flag EXCEPT ![i] = FALSE] /\ turn' = j Next == \E i \in {0, 1}: Enter(i) \/ SetTurn(i) \/ EnterCS(i) \/ ExitCS(i) (* Mutual exclusion property *)MutualExclusion == ~(pc[0] = "cs" /\ pc[1] = "cs") (* Specification *)Spec == Init /\ [][Next]_vars /\ WF_vars(Next) THEOREM Spec => []MutualExclusion =========================================================================Industrial Use of Verification
Formal verification is used in high-stakes domains:
- Hardware verification: CPU designs are now routinely model-checked
- Aerospace: Flight control systems require formal proofs
- Security protocols: TLS, authentication protocols are verified
- Distributed systems: Amazon uses TLA+ for DynamoDB, S3
- Compilers: CompCert is a formally verified C compiler
For synchronization specifically, libraries like liblfds (lock-free data structures) have undergone formal verification.
Formal verification is most valuable when: (1) Correctness is critical (safety, security, financial), (2) Testing is insufficient (concurrency, rare conditions), (3) The system will run for long periods without restart, (4) Bugs have severe consequences (data loss, safety hazards). For synchronization algorithms—especially in OS kernels—formal verification is increasingly standard.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.