π§ L6: Process Synchronization
Lecture Goal
Understand why concurrent processes need coordination, and master the tools (Critical Sections, Semaphores) that prevent race conditions, deadlocks, and starvation.
The Big Picture
Synchronization is one of the trickiest topics in OS β not because the ideas are complex individually, but because reasoning about interleaved execution trips up almost everyone. Take it slow, draw timelines, and never assume order!
1. Race Conditions
π€ The Core Problem
When two or more processes execute concurrently and share a modifiable resource, the final result can depend on the precise order of their operations. This unpredictability is called a race condition.
flowchart LR subgraph ID ["Sequential Execution β "] P1A[P1: LOAD X] --> P1B[P1: ADD 1000] --> P1C[P1: STORE X] P1C --> P2A[P2: LOAD X] --> P2B[P2: ADD 1000] --> P2C[P2: STORE X] end style P1A fill:#99ff99 style P2C fill:#99ff99
A single sequential process is deterministic β run it 100 times and you get the same result. But concurrent processes are non-deterministic: the OS can interleave their instructions in many ways.
βοΈ Why High-Level Code Isnβt Atomic
A single statement like X = X + 1000 compiles to three machine instructions:
flowchart LR A["X = X + 1000"] --> B["1. LOAD X β Register"] B --> C["2. ADD 1000 to Register"] C --> D["3. STORE Register β X"] style A fill:#ffcc99 style D fill:#ff9999
| Step | Instruction | What Happens |
|---|---|---|
| 1 | LOAD X β Register | Read X from memory into CPU |
| 2 | ADD 1000 | Add 1000 to register value |
| 3 | STORE Register β X | Write result back to memory |
The race happens between steps 3 and 3:
| Scenario | Execution | Final X |
|---|---|---|
| β Good | P1 runs 1β2β3, then P2 runs 1β2β3 | 2345 (correct!) |
| β Bad | P1 runs 1β2, P2 runs 1β2β3, P1 runs 3 | 1345 (P2βs update lost!) |
The Lost Update
In the bad scenario, P2 loaded the old value of X before P1 stored its result. Both processes compute βnewβ values from the same βoldβ value β one update is lost.
π Key Terminology
| Term | Definition |
|---|---|
| Concurrent execution | Multiple processes making progress within overlapping time periods |
| Race condition | A bug where the outcome depends on non-deterministic ordering of operations |
| Shared modifiable resource | A variable, buffer, or data structure accessible and changeable by multiple processes |
| Deterministic | Same input β same output, every time |
| Non-deterministic | Output varies across runs due to timing/interleaving |
| Interleaving | The OS switching between processes mid-execution |
β οΈ Common Pitfalls
Pitfall 1: "High-level code = one instruction"
Students often assume
X = X + 1is atomic. It is NOT. At the machine level itβs always multiple steps. Any step can be interrupted by a context switch.
Pitfall 2: "Race conditions are rare"
They can be rare in practice (manifesting only under specific timing), making them extremely hard to debug. Never assume your concurrent code is correct just because it passes a few test runs!
Pitfall 3: "Read-only access is safe"
Race conditions require modification. Two processes that only read a shared variable are safe. The race only happens when at least one process writes.
β Mock Exam Questions
Q1: Possible Values
Given
X = 10, two processes P1 and P2 each executeX = X * 2concurrently. Which values are possible?Answer
Answer:
- If sequential: X = 10 β 20 β 40 (or 10 β 20 β 40)
- If interleaved badly: Both load 10, both compute 20, both store 20
Possible final values: 20 or 40
Q2: Required Conditions
Which condition is NOT required for a race condition?
Answer
Answer: The processes are in the same address space
Required: concurrent execution, shared resource, at least one writer. Not required: same address space (processes can share memory via IPC).
Q3: Machine Instructions
The statement
count++in C translates to how many machine instructions?Answer
Answer: 3
- LOAD count β register
- ADD 1 to register
- STORE register β count
2. Critical Section (CS)
π The Solution: Protected Code Regions
The solution to race conditions is to identify the βdangerousβ part of the code and wrap it in protection. This protected region is the Critical Section.
flowchart LR A[Normal Code] --> B[Enter CS] B --> C["π Critical Section"] C --> D[Exit CS] D --> E[Normal Code] style C fill:#ff9999,stroke:#333,stroke-width:2px style B fill:#ffcc99 style D fill:#ffcc99
Generic skeleton:
// Normal code (no shared resources)
Enter CS(); // Request permission to enter
// Critical Work // Only ONE process here at a time!
Exit CS(); // Signal departure
// Normal code (no shared resources)β The Four Required Properties
For a CS implementation to be correct, it must satisfy all four properties:
| Property | Description | Violation = |
|---|---|---|
| Mutual Exclusion | Only one process in the CS at any time | Race condition |
| Progress | If no process is in the CS, a waiting process should eventually enter | Deadlock |
| Bounded Wait | After requesting entry, a process waits at most N times before entering | Starvation |
| Independence | A process NOT in the CS should never block other processes | Poor performance |
flowchart TD CS[Critical Section Properties] CS --> ME["Mutual Exclusion<br/>Only one at a time"] CS --> P["Progress<br/>Someone enters if CS empty"] CS --> BW["Bounded Wait<br/>No infinite waiting"] CS --> I["Independence<br/>Non-CS code doesn't block others"] style CS fill:#ffcc99 style ME fill:#99ff99 style P fill:#99ff99 style BW fill:#99ff99 style I fill:#99ff99
π¨ Symptoms of Broken Implementations
| Symptom | Description | Analogy |
|---|---|---|
| π΄ Deadlock | ALL processes blocked β no one makes progress | Four cars at a 4-way stop, all waiting for each other |
| π‘ Livelock | Processes βactiveβ but make no real progress | Two people in a hallway, both stepping aside repeatedly |
| π Starvation | Some processes blocked forever while others proceed | The VIP line gets served, you wait forever |
Bathroom Analogy π½
Think of the CS like a single-occupancy bathroom:
- Mutual exclusion = only one person inside
- Progress = if empty, next person in line gets in
- Bounded wait = you donβt wait forever
- Independence = people not waiting donβt block the line
β οΈ Common Pitfalls
Pitfall 1: Confusing Deadlock and Starvation
In a deadlock, ALL processes are stuck β nothing moves. In starvation, some processes DO make progress, but one poor process is perpetually skipped.
Pitfall 2: Forgetting Independence
Students often only check mutual exclusion. But if a process outside the CS can block one inside, your implementation fails β even if mutual exclusion holds.
Pitfall 3: Livelock β Deadlock
In a livelock, processes are NOT blocked (from the OSβs perspective they are running), but they accomplish nothing. Classic example: two polite people keep saying βafter youβ forever.
β Mock Exam Questions
Q4: Required Properties
Which is NOT a required property of a correct CS implementation?
Answer
Answer: FIFO ordering
Required: Mutual Exclusion, Progress, Bounded Wait. FIFO ordering is nice but not required β processes donβt need to enter in order.
Q5: Symptom Identification
All 3 processes are waiting forever at
Wait(mutex)and none are in the CS. This is:Answer
Answer: C Deadlock
All processes blocked, none making progress = deadlock.
Q6: Property Violation
Process P0 is outside the CS. Due to a bug, P0 holds a lock that prevents P1 from entering the CS. Which property is violated?
Answer
Answer: D Independence
A process not in the CS should never block others from entering.
3. CS Implementations β Assembly Level
π§ Hardware Atomic Instructions
The most fundamental CS implementation uses special atomic hardware instructions provided by the processor.
TestAndSet (TAS) is the canonical example:
flowchart LR subgraph TAS ["TestAndSet (Atomic)"] A[Read MemoryLocation] --> B[Store 1 to MemoryLocation] end TAS --> C["Return old value"] style TAS fill:#ffcc99
// TestAndSet behavior (performed as ONE indivisible operation)
int TestAndSet(int* Lock) {
int old_value = *Lock; // Step 1: Read
*Lock = 1; // Step 2: Set to 1
return old_value; // Return what we read
}Because this is atomic, no other process can interrupt between steps 1 and 2.
π Spinlock Implementation
// Lock = 0 means "CS is free", Lock = 1 means "CS is occupied"
void EnterCS(int* Lock) {
while (TestAndSet(Lock) == 1); // Spin until we "win"
}
void ExitCS(int* Lock) {
*Lock = 0; // Release the lock
}How it works:
| Situation | What Happens |
|---|---|
| Lock = 0 (free) | TAS reads 0 (we win!), sets Lock to 1, while-loop exits, we enter CS |
| Lock = 1 (busy) | TAS reads 1 (we lose), sets Lock back to 1 (no change), we loop and try again |
flowchart TD Start[Call EnterCS] --> TAS{TestAndSet Lock} TAS -- "Returns 0" --> Enter[Enter Critical Section] TAS -- "Returns 1" --> Spin[Spin: Try Again] Spin --> TAS Enter --> Work[Do Critical Work] Work --> Exit[ExitCS: Set Lock = 0] Exit --> Done[Done] style Enter fill:#99ff99 style Spin fill:#ffcc99
Properties satisfied: β Mutual Exclusion, β Progress, β Bounded Wait
Key downside: π΄ Busy Waiting β the waiting process keeps consuming CPU cycles checking the lock. This is wasteful!
π Other Atomic Instructions
| Instruction | Description |
|---|---|
| Compare and Exchange | Atomically compare a value and swap only if it matches |
| Atomic Swap | Atomically exchange two values |
| Load-Link / Store-Conditional | LL reads a value; SC stores only if no one else touched it |
β οΈ Common Pitfalls
Pitfall 1: Thinking TAS is "two steps"
The power of TAS is that READ and SET happen as ONE indivisible instruction. You cannot separate them mentally when analyzing correctness.
Pitfall 2: Forgetting busy waiting is a problem
TAS works correctly! But it wastes CPU. If the process holding the CS is long-running, the spinning process wastes potentially thousands of CPU cycles doing nothing.
β Mock Exam Questions
Q7: Why Software TAS Fails
What makes
TestAndSetsafe for mutual exclusion when implemented purely in software NOT possible?Answer
Answer: Context switches can occur between any two software instructions
Without hardware atomicity, a context switch can occur between the read and the write, allowing another process to interfere.
Q8: ExitCS Behavior
After
ExitCSis called, the Lock value becomes:Answer
Answer: 0
ExitCS sets Lock = 0 to release it, signaling the CS is free.
Q9: Spinlock Drawback
The main drawback of a spinlock is:
Answer
Answer: It wastes CPU cycles while waiting
The spinning process keeps checking the lock instead of doing useful work or sleeping.
4. CS Implementations β High Level Language
π¨ Three Failed Attempts (And One That Works)
Can we implement a CS using only ordinary programming constructs? Letβs see the evolution:
β Attempt 1: Single Lock Variable
// Both P0 and P1:
while (Lock != 0); // Wait until free
Lock = 1; // Claim the lock
// Critical Section
Lock = 0; // ReleaseProblem: Both processes can see Lock = 0 simultaneously, pass the while, then BOTH set Lock = 1 and enter the CS.
flowchart LR P0["P0: while(Lock!=0) passes"] --> P0a["P0: Lock=1"] P1["P1: while(Lock!=0) passes"] --> P1a["P1: Lock=1"] P0a --> Both["Both in CS! β"] style Both fill:#ff9999
Mutual Exclusion violated!
β Attempt 2: Turn Variable (Strict Alternation)
// P0: // P1:
while (Turn != 0); while (Turn != 1);
// CS // CS
Turn = 1; Turn = 0;Problem: Processes must strictly alternate. If P0 never wants to enter the CS again, P1 can NEVER enter β even though the CS is empty!
flowchart LR P0["P0: Turn != 0? No, enters CS"] --> P0done["P0: Done, sets Turn=1"] P0done --> P0never["P0: Never wants CS again"] P1["P1: Turn != 1? Yes, waits forever"] style P1 fill:#ff9999
Independence violated β Starvation!
β Attempt 3: Want Array
// P0: // P1:
Want[0] = 1; Want[1] = 1;
while (Want[1]); while (Want[0]);
// CS // CS
Want[0] = 0; Want[1] = 0;Problem: Both set their Want flag simultaneously, then both block on the while. Neither can proceed.
flowchart TD P0w["P0: Want[0]=1"] --> P0b["P0: while(Want[1])... BLOCKS"] P1w["P1: Want[1]=1"] --> P1b["P1: while(Want[0])... BLOCKS"] P0b --> Deadlock["Both waiting! β"] P1b --> Deadlock style Deadlock fill:#ff9999
Deadlock!
β Petersonβs Algorithm (Correct!)
// P0: // P1:
Want[0] = 1; Turn = 1; Want[1] = 1; Turn = 0;
while (Want[1] && Turn == 1); while (Want[0] && Turn == 0);
// CS // CS
Want[0] = 0; Want[1] = 0;Key insight: The Turn variable breaks ties.
flowchart TD subgraph Peterson ["Peterson's Algorithm"] W0["Want[0] = 1"] --> T0["Turn = 1 (yield to P1)"] T0 --> C0{"Want[1] && Turn == 1?"} C0 -- "Yes" --> Spin0["P0 spins"] C0 -- "No" --> Enter0["P0 enters CS"] W1["Want[1] = 1"] --> T1["Turn = 0 (yield to P0)"] T1 --> C1{"Want[0] && Turn == 0?"} C1 -- "Yes" --> Spin1["P1 spins"] C1 -- "No" --> Enter1["P1 enters CS"] end style Enter0 fill:#99ff99 style Enter1 fill:#99ff99
| Property | Status |
|---|---|
| Mutual Exclusion | β Only one enters |
| Progress | β Someone enters if CS empty |
| Bounded Wait | β Finite waiting time |
| Independence | β Non-CS code doesnβt block |
Assumption: Writing to Turn is atomic (true on most architectures).
Remaining problems: π΄ Busy waiting, π΄ Only works for 2 processes
β οΈ Common Pitfalls
Pitfall 1: "Strict alternation is fair"
But if P0 finishes early and never enters the CS again, P1 is starved forever. Independence is violated.
Pitfall 2: Attempt 3's deadlock is hard to see
The deadlock requires both processes to execute
Want[i] = 1before either checkswhile(Want[other]). This is a valid interleaving β always check for it!
Pitfall 3: Peterson's Turn assignment
In Petersonβs, P0 sets
Turn = 1(yielding to P1) and P1 setsTurn = 0(yielding to P0). This is backwards from what students expect!
β Mock Exam Questions
Q10: Peterson's Requirement
Petersonβs Algorithm requires:
Answer
Answer: That writing to
Turnis atomicNo hardware support needed beyond atomic writes to a single variable.
Q11: Strict Alternation Violation
Attempt 2 (strict alternation) violates which property?
Answer
Answer: Independence
A process that doesnβt want the CS can block another process from entering.
Q12: Want Array Deadlock
What execution sequence causes deadlock in Attempt 3?
Answer
Answer:Both set
Want[i] = 1before either checks the while loopIf both signal intent before checking, both see the otherβs flag and both wait forever.
5. High Level Abstraction: Semaphores
π« The Elegant Solution
Semaphores, proposed by Edsger W. Dijkstra in 1965, are the fundamental high-level synchronization primitive.
A semaphore S consists of:
- An integer value (initialized to non-negative)
- A list of waiting processes (sleeping, not spinning)
flowchart LR subgraph Semaphore ["Semaphore S"] V["Integer Value"] W["Wait List"] end Wait["Wait(S)"] --> Check{"S > 0?"} Check -- "Yes" --> Dec["Decrement S, proceed"] Check -- "No" --> Block["Block! Add to wait list"] Signal["Signal(S)"] --> Inc["Increment S"] Inc --> Wake{"Wait list empty?"} Wake -- "No" --> WakeOne["Wake one process"] style Semaphore fill:#ffcc99 style Block fill:#ff9999 style Dec fill:#99ff99
Two atomic operations:
| Operation | Also called | Behavior |
|---|---|---|
Wait(S) | P(), Down() | If S β€ 0: block. Decrement S. |
Signal(S) | V(), Up() | Increment S. Wake one sleeping process if any. Never blocks. |
Ticket Analogy ποΈ
Think of
Sas the number of available βtickets.β
Waittakes a ticket (blocking if none available)Signalreturns a ticket (and wakes someone up)
π The Semaphore Invariant
This invariant is the key to proving correctness!
π Types of Semaphores
| Type | Values | Also called | Use case |
|---|---|---|---|
| Binary semaphore | 0 or 1 | Mutex | Mutual exclusion |
| General semaphore | 0, 1, 2, β¦ | Counting semaphore | Resource counting, signaling |
π Using Semaphores for Mutual Exclusion
// Semaphore s = 1 (binary)
Wait(s);
// Critical Section
Signal(s);Proof of Mutual Exclusion:
Let = number of processes currently in the CS.
Since , we have . β Mutual exclusion proven!
No deadlock? If all processes at Wait(S) with no one in CS β and β . Contradiction! β
β οΈ Incorrect Semaphore Usage = Deadlock
// P0: // P1:
Wait(P) Wait(Q)
Wait(Q) Wait(P) // Reversed order!
// CS // CS
Signal(Q) Signal(P)
Signal(P) Signal(Q)flowchart LR P0["P0: Wait(P) β"] --> P0b["P0: Wait(Q)... blocks"] P1["P1: Wait(Q) β"] --> P1b["P1: Wait(P)... blocks"] P0b --> Dead["π DEADLOCK"] P1b --> Dead style Dead fill:#ff0000,color:#fff
β οΈ Common Pitfalls
Pitfall 1: Forgetting that Wait can BLOCK
Wait(S)is not just a decrement! If S β€ 0, the calling process goes to sleep. Students often trace scenarios forgetting that blocked processes stop executing.
Pitfall 2: Signal never blocks
Signal(S)always completes immediately. It increments S and optionally wakes someone β it never waits.
Pitfall 3: The order of Wait calls matters
Calling
Wait(A)thenWait(B)in different orders across processes is the classic recipe for deadlock. Always acquire locks in the same order!
Pitfall 4: Applying the invariant incorrectly
#wait(S)in the invariant counts only completed waits, not total calls. A blocked processβs wait is NOT complete yet.
β Mock Exam Questions
Q13: Semaphore Value
Semaphore
Sis initialized to 3. After 5Waitoperations and 2Signaloperations (all completed), what isS?Answer
Answer: 0
Q14: Blocking Operation
Which operation on a semaphore can cause the calling process to block?
Answer
Answer:
Wait(S)only
Signal(S)always returns immediately.
Q15: Signaling Pattern
A semaphore initialized to 0 is used between Producer and Consumer. What pattern is this?
Answer
Answer: Producer-consumer signaling
The producer signals when data is ready; consumer waits for data.
6. Classical Synchronization Problems
π Producer-Consumer Problem
Setup: Processes share a bounded buffer of size K.
- Producers add items β but only when buffer is not full (< K items)
- Consumers remove items β but only when buffer is not empty (> 0 items)
flowchart LR subgraph Buffer ["Bounded Buffer (Size K)"] B1["Item"] B2["Item"] B3["..."] B4["Empty"] B5["Empty"] end P["Producer"] -->|"produce"| Buffer Buffer -->|"consume"| C["Consumer"] style Buffer fill:#ffcc99
β Blocking Solution
// Semaphores:
// mutex = S(1) β Mutual exclusion for buffer access
// notFull = S(K) β Tracks available slots (initially K)
// notEmpty = S(0) β Tracks available items (initially 0)
// Producer: // Consumer:
while (TRUE) { while (TRUE) {
Produce Item; wait(notEmpty);
wait(notFull); wait(mutex);
wait(mutex); item = buffer[out];
buffer[in] = item; out = (out+1) % K;
in = (in+1) % K; signal(mutex);
signal(mutex); signal(notFull);
signal(notEmpty); Consume Item;
} }| Semaphore | Initial Value | Purpose |
|---|---|---|
mutex | 1 | Mutual exclusion on buffer operations |
notFull | K | Blocks producer when buffer is full |
notEmpty | 0 | Blocks consumer when buffer is empty |
Critical Order!
wait(notFull)must come BEFOREwait(mutex)in the producer. If reversed and buffer is full β deadlock!
π Readers-Writers Problem
Setup: Shared data structure D.
- Multiple readers can access D simultaneously (reading doesnβt modify)
- Writers need exclusive access (no other reader or writer allowed)
// roomEmpty = S(1), mutex = S(1), nReader = 0
// Writer: // Reader:
wait(roomEmpty); wait(mutex);
Modify D; nReader++;
signal(roomEmpty); if (nReader == 1)
wait(roomEmpty); // First reader locks writers
signal(mutex);
Reads D;
wait(mutex);
nReader--;
if (nReader == 0)
signal(roomEmpty); // Last reader unlocks writers
signal(mutex);Problem: π΄ Writer starvation! If readers continuously arrive, nReader never hits 0, and the writer is blocked forever.
π½οΈ Dining Philosophers Problem
Setup: 5 philosophers, 5 chopsticks (one between each pair). Each philosopher needs both adjacent chopsticks to eat.
flowchart TD subgraph Table ["Round Table"] P0[Philosopher 0] P1[Philosopher 1] P2[Philosopher 2] P3[Philosopher 3] P4[Philosopher 4] end C0[Chopstick 0] --- P0 C1[Chopstick 1] --- P1 C2[Chopstick 2] --- P2 C3[Chopstick 3] --- P3 C4[Chopstick 4] --- P4
β Attempt 1: Grab Left, Then Right
Each philosopher grabs left chopstick first, then right. If ALL grab left simultaneously β deadlock!
flowchart LR P0["P0 has C0"] -->|"waits for"| C1["C1 held by P1"] P1["P1 has C1"] -->|"waits for"| C2["C2 held by P2"] P2["P2 has C2"] -->|"waits for"| C3["C3 held by P3"] P3["P3 has C3"] -->|"waits for"| C4["C4 held by P4"] P4["P4 has C4"] -->|"waits for"| C0["C0 held by P0"] style P0 fill:#ff9999 style P1 fill:#ff9999 style P2 fill:#ff9999 style P3 fill:#ff9999 style P4 fill:#ff9999
β Solution: Limited Eater
Allow only 4 philosophers at the table simultaneously using seats = S(4):
void philosopher(int i) {
while (TRUE) {
Think();
wait(seats); // Limit to 4 at table
wait(chpStk[LEFT]);
wait(chpStk[RIGHT]);
Eat();
signal(chpStk[LEFT]);
signal(chpStk[RIGHT]);
signal(seats);
}
}Why does this work?
Pigeonhole Principle: With 5 chopsticks and only 4 philosophers, at least one philosopher always has both chopsticks available. β
β οΈ Common Pitfalls
Pitfall 1: Swapping wait order in Producer-Consumer
If a producer calls
wait(mutex)THENwait(notFull)when buffer is full β holdsmutexwhile sleeping β consumer can never acquiremutexβ deadlock!
Pitfall 2: Writer starvation in Readers-Writers
The simple solution is technically correct per spec, but writers can starve. Real systems need priority mechanisms.
Pitfall 3: Circular wait in Dining Philosophers
P0 grabs C0, P1 grabs C1, P2 grabs C2, P3 grabs C3, P4 grabs C4. Now everyone waits for their right chopstick. Perfect circular deadlock.
β Mock Exam Questions
Q16: notFull Initialization
In Producer-Consumer,
notFullis initialized to K because:Answer
Answer: The buffer has K available slots initially
Each successful producer
wait(notFull)βreservesβ a slot. Initially all K slots are available.
Q17: nReader Protection
Why is
nReaderprotected bymutexin Readers-Writers?Answer
Answer:
nReaderitself is a shared variable that can raceMultiple readers can modify
nReadersimultaneously β it needs protection too.
Q18: Limited Eater
What happens if
seats = S(5)instead ofS(4)?Answer
Answer: Deadlock is possible again
With 5 philosophers allowed, all can grab their left chopstick simultaneously β deadlock.
7. Synchronization Implementations (POSIX)
π οΈ Real-World Implementation
In practice, semaphores are implemented by the OS. The most common standard is POSIX.
POSIX Semaphores (Unix)
#include <semaphore.h>
// Compile with: gcc something.c -lrt
sem_t s;
sem_init(&s, 0, 1); // Initialize: pshared=0 (process-local), value=1
sem_wait(&s); // Wait()
sem_post(&s); // Signal()
sem_destroy(&s); // Cleanuppthread Mutex and Condition Variables
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&m); // Wait() β binary semaphore
pthread_mutex_unlock(&m); // Signal()
pthread_cond_t cv;
pthread_cond_wait(&cv, &m); // Wait for event (releases mutex!)
pthread_cond_signal(&cv); // Wake one waiter
pthread_cond_broadcast(&cv); // Wake ALL waitersLanguage support summary:
| Language | Mutex | Semaphore | Condition Variable |
|---|---|---|---|
| Java | synchronized, ReentrantLock | Semaphore class | wait(), notify(), notifyAll() |
| Python | threading.Lock | threading.Semaphore | threading.Condition |
| C++ (C++11+) | std::mutex | (use with condition_variable) | std::condition_variable |
| C (POSIX) | pthread_mutex_t | sem_t | pthread_cond_t |
π Connections
| π Lecture | π Connection |
|---|---|
| L7 | How Memory Abstraction Works |
The Key Insight
Synchronization is all about reasoning carefully about all possible interleavings. When in doubt, draw a timeline, enumerate cases, and apply the invariants. Youβve got this! πͺ