Operating Systems
Unit 4: Process Synchronization & Threads
From race conditions to thread-safe code โ master critical sections, semaphores, classical synchronization problems, and multithreading to build reliable concurrent systems.
โฑ๏ธ Time to Complete: 7 hrs theory + 5 hrs lab | ๐ฐ Earning Potential: โน8Kโโน25K/month | ๐ 30 MCQs (Bloom's Mapped)
๐ผ Jobs this unlocks: Backend Engineer (โน6โ12 LPA) | Systems Programmer (โน8โ15 LPA)
Opening Hook โ Race Condition in Action!
๐ฌ The BookMyShow Bug That Sold One Ticket Twice
Picture this: Friday evening, 7 PM. Rahul in Mumbai and Sneha in Pune are both staring at the last available ticket for Jawan at PVR Phoenix. Both see "1 seat available." Both click "Pay Now" through Paytm at the exact same millisecond.
The payment gateway processes both requests. Both receive confirmation SMS: "๐ซ Booking confirmed!" But there's only ONE seat. When Rahul arrives at the theatre, his ticket is invalid. Chaos. Refunds. 1-star reviews.
What went wrong? The code that checks seat availability and deducts the seat count was NOT protected. Two threads read the same value (seats = 1), both passed the check (1 > 0), both decremented (seats = 0), but the seat was sold twice. This is a race condition โ and it's exactly what process synchronization prevents.
Every payment gateway (Razorpay, Paytm, PhonePe), every booking system (IRCTC, BookMyShow, MakeMyTrip), every banking app runs concurrent code. Without synchronization, your money isn't safe. This chapter teaches you how to make it safe.
Learning Outcomes โ Bloom's Taxonomy Mapped
| Bloom's Level | Learning Outcome |
|---|---|
| ๐ต Remember | List the 3 requirements of the critical section problem; define semaphore, mutex, monitor, and thread |
| ๐ต Understand | Explain how Peterson's Algorithm ensures mutual exclusion; differentiate binary vs counting semaphores with Indian analogies |
| ๐ข Apply | Implement Producer-Consumer using POSIX semaphores in C; write Pthreads programs with mutex locks |
| ๐ข Analyze | Trace race conditions in concurrent code; compare user-level vs kernel-level threads; analyze deadlock scenarios in Dining Philosophers |
| ๐ Evaluate | Evaluate which synchronization primitive is best for a given real-world system (banking, booking, etc.) |
| ๐ Create | Design a thread-safe booking system; implement Dining Philosophers with deadlock detection |
Concept Explanation โ Process Synchronization & Threads from Scratch
1. Concurrent & Cooperating Processes
Concurrent processes are processes that execute during overlapping time periods. They may run truly in parallel on multiple CPU cores, or interleave on a single core via context switching.
Processes are of two types based on interaction:
| Type | Definition | Example |
|---|---|---|
| Independent | Cannot affect or be affected by other processes. No shared data. | Two students writing separate exams in different rooms |
| Cooperating | Can affect or be affected by other processes. Share data/resources. | Two chefs sharing one stove in a kitchen โ they must coordinate |
Why cooperating processes?
- Information sharing: Multiple users accessing a shared database (e.g., multiple IRCTC agents selling same train seats)
- Computation speedup: Breaking a task into sub-tasks that run in parallel
- Modularity: Dividing system functions into separate processes
- Convenience: A user may run editor, compiler, and browser simultaneously
2. The Critical Section Problem
A critical section is a segment of code where a process accesses shared resources (variables, files, databases). If two processes enter their critical sections simultaneously, data corruption can occur.
๐ Structure of a Process with Critical Section
Pseudocode do { /* ENTRY SECTION โ request permission to enter */ CRITICAL SECTION /* Access shared resource here */ /* EXIT SECTION โ release the lock */ REMAINDER SECTION /* Non-critical code */ } while (TRUE);
Three Requirements for a Valid Solution
| # | Requirement | Meaning | Indian Analogy |
|---|---|---|---|
| 1 | Mutual Exclusion | If process Pi is in its critical section, no other process can be in its critical section | Only ONE person in the ATM booth at a time โ door is locked from inside |
| 2 | Progress | If no process is in its critical section and some processes wish to enter, the selection cannot be postponed indefinitely | If the ATM booth is empty and someone is waiting, they must be allowed in โ you can't say "come back tomorrow" |
| 3 | Bounded Waiting | There must be a limit on the number of times other processes can enter before a waiting process gets its turn | In a bank queue, you get a token number โ you WILL be served; nobody can keep cutting in front of you forever |
3. Software Solutions: Peterson's Algorithm & Bakery Algorithm
Peterson's Algorithm (2 Processes)
Peterson's Algorithm is an elegant software-only solution for 2 processes. It uses two shared variables: a flag[] array (indicating intent) and a turn variable (tie-breaker).
๐งฎ Peterson's Algorithm โ Pseudocode
Pseudocode // Shared variables bool flag[2] = {false, false}; // flag[i] = true means process i wants to enter int turn; // whose turn it is (tie-breaker) // Process P_i (i = 0 or 1, j = 1 - i) do { flag[i] = true; // I want to enter turn = j; // But I give you the chance first while (flag[j] && turn == j) ; // Busy wait โ spin until it's my turn /* CRITICAL SECTION */ flag[i] = false; // I'm done, leaving CS /* REMAINDER SECTION */ } while (TRUE);
Trace: How Peterson's Algorithm Works
Let's trace with P0 and P1 both wanting to enter:
- P0 sets
flag[0] = true,turn = 1(gives P1 priority) - P1 sets
flag[1] = true,turn = 0(gives P0 priority) - P0 checks:
flag[1] == trueANDturn == 1?turnis 0, NOT 1 โ P0 enters CS โ - P1 checks:
flag[0] == trueANDturn == 0? YES โ P1 waits โณ - When P0 finishes, sets
flag[0] = falseโ P1's while loop exits โ P1 enters CS โ
Correctness: โ Mutual Exclusion (only one enters) โ Progress (if one doesn't want, other enters immediately) โ Bounded Waiting (at most 1 wait)
Bakery Algorithm (N Processes)
Lamport's Bakery Algorithm generalises the idea to N processes. It's named after the take-a-number system used in bakeries (or SBI banks!).
๐ Bakery Algorithm โ Pseudocode (N processes)
Pseudocode // Shared variables bool choosing[n]; // choosing[i] = true means process i is picking a number int number[n]; // number[i] = ticket number of process i (0 = not interested) // Process P_i do { choosing[i] = true; number[i] = 1 + max(number[0], ..., number[n-1]); // Take next ticket choosing[i] = false; for (j = 0; j < n; j++) { while (choosing[j]) ; // Wait if process j is choosing while (number[j] != 0 && (number[j], j) < (number[i], i)) ; // Wait if j has a smaller ticket (or same ticket but smaller ID) } /* CRITICAL SECTION */ number[i] = 0; // Done โ release ticket /* REMAINDER SECTION */ } while (TRUE);
4. Hardware Synchronization Primitives: TAS & CAS
Software solutions have limitations on modern hardware. CPU manufacturers provide atomic instructions โ instructions that execute as a single, uninterruptible unit.
Test-and-Set (TAS)
โ๏ธ Test-and-Set โ Hardware Instruction
C-style Pseudocode // This ENTIRE function executes atomically (hardware guarantee) bool test_and_set(bool *target) { bool rv = *target; // Save old value *target = true; // Set to true return rv; // Return old value } // Usage โ mutual exclusion for any number of processes bool lock = false; // Shared lock variable do { while (test_and_set(&lock)) ; // Spin until lock is acquired /* CRITICAL SECTION */ lock = false; // Release lock /* REMAINDER SECTION */ } while (TRUE);
How it works: If lock is false (free), TAS returns false (loop exits) and sets lock = true atomically. If lock is true (held), TAS returns true (keeps spinning).
Compare-and-Swap (CAS)
โ๏ธ Compare-and-Swap โ Hardware Instruction
C-style Pseudocode // Executes atomically int compare_and_swap(int *value, int expected, int new_value) { int temp = *value; if (*value == expected) *value = new_value; return temp; } // Usage int lock = 0; do { while (compare_and_swap(&lock, 0, 1) != 0) ; // Spin until lock acquired /* CRITICAL SECTION */ lock = 0; /* REMAINDER SECTION */ } while (TRUE);
Advantage of CAS over TAS: CAS only modifies the value if it matches the expected value, making it more flexible. It's the foundation of lock-free data structures used in high-performance systems like Zerodha's trading engine.
5. Semaphores
A semaphore is a synchronization tool that does not require busy waiting (in its proper implementation). It's an integer variable accessed only through two atomic operations: P() (wait/down) and V() (signal/up).
๐ฆ Semaphore Operations โ P() and V()
Pseudocode // P() operation โ also called wait() or down() P(S) { while (S <= 0) ; // Wait (in practice: block/sleep) S--; } // V() operation โ also called signal() or up() V(S) { S++; }
Key rule: Both P() and V() are atomic โ they execute without interruption.
Binary Semaphore (Mutex) vs Counting Semaphore
| Feature | Binary Semaphore (Mutex) | Counting Semaphore |
|---|---|---|
| Value range | 0 or 1 | 0 to N (any non-negative integer) |
| Purpose | Mutual exclusion โ only ONE process in CS | Control access to a resource with N instances |
| Analogy | Single bathroom key โ only 1 person at a time | Parking lot with N spaces โ N cars can enter |
| Init value | 1 (unlocked) | N (number of available resources) |
P() = take a token and enter (or wait). V() = return token and leave.
Pseudocode // Mutual Exclusion using Binary Semaphore semaphore mutex = 1; // Process P_i do { P(mutex); // Entry section โ acquire lock /* CRITICAL SECTION */ V(mutex); // Exit section โ release lock /* REMAINDER SECTION */ } while (TRUE);
wait() and signal() instead. In POSIX C, they're sem_wait() and sem_post().
6. Monitors
A monitor is a high-level synchronization construct โ essentially a class/module where only ONE process can be active inside at any time. Think of it as automatic mutual exclusion built into the data structure.
๐๏ธ Monitor Structure
Pseudocode monitor MonitorName { // Shared data โ private to monitor int shared_var; // Condition variables condition x, y; // Procedures โ only one can execute at a time procedure P1() { // ... access shared_var safely ... if (need_to_wait) x.wait(); // Suspend and release monitor } procedure P2() { // ... modify shared_var ... x.signal(); // Wake up one waiting process } // Initialization code init() { shared_var = 0; } }
Condition Variables
x.wait()โ The calling process is suspended and placed in a queue for conditionx. The monitor lock is released so other processes can enter.x.signal()โ Resumes exactly ONE process waiting on conditionx. If no process is waiting, signal has no effect (unlike semaphore V which always increments).
Key difference from semaphores: A semaphore's V() always increments the count even if nobody is waiting. A monitor's signal() is lost if nobody is waiting โ it does NOT accumulate.
synchronized keyword implements monitors. When you write synchronized(obj) { ... } in Java, you're creating a monitor. The JVM ensures only one thread executes inside that block at a time. Android apps, which run on JVM, use this extensively.
7. Classical Synchronization Problems
7.1 Producer-Consumer Problem (Bounded Buffer)
Scenario: A producer process generates data and puts it into a buffer. A consumer process takes data from the buffer. The buffer has a fixed size N.
Constraints:
- Producer must wait if buffer is FULL
- Consumer must wait if buffer is EMPTY
- Only one process can access the buffer at a time (mutual exclusion)
๐ฆ Producer-Consumer โ Semaphore Solution
Pseudocode // Shared semaphore mutex = 1; // Mutual exclusion for buffer access semaphore empty = N; // Count of empty slots (initially N) semaphore full = 0; // Count of filled slots (initially 0) // PRODUCER do { // ... produce an item ... P(empty); // Wait for an empty slot P(mutex); // Lock the buffer // ... add item to buffer ... V(mutex); // Unlock the buffer V(full); // Signal that a filled slot is available } while (TRUE); // CONSUMER do { P(full); // Wait for a filled slot P(mutex); // Lock the buffer // ... remove item from buffer ... V(mutex); // Unlock the buffer V(empty); // Signal that an empty slot is available // ... consume the item ... } while (TRUE);
P(mutex) before P(empty), and the buffer is full, the producer holds mutex and waits on empty โ but the consumer needs mutex to remove an item, causing deadlock! Always acquire counting semaphore BEFORE mutex.
7.2 Reader-Writer Problem
Scenario: A database is shared among processes. Readers only read data; writers modify data. Multiple readers can read simultaneously, but a writer needs exclusive access.
Reader Preference Solution (First Readers-Writers)
๐ Reader-Writer โ Reader Preference
Pseudocode // Shared semaphore rw_mutex = 1; // Exclusive access for writers semaphore mutex = 1; // Protect read_count int read_count = 0; // Number of active readers // WRITER do { P(rw_mutex); // Get exclusive access // ... write data ... V(rw_mutex); // Release } while (TRUE); // READER do { P(mutex); read_count++; if (read_count == 1) P(rw_mutex); // First reader locks out writers V(mutex); // ... read data ... P(mutex); read_count--; if (read_count == 0) V(rw_mutex); // Last reader unlocks for writers V(mutex); } while (TRUE);
Problem: If readers keep arriving, writers may starve (never get access). This is called reader preference.
Writer Preference: Add another semaphore so that once a writer is waiting, no new readers are admitted. This prevents writer starvation but may starve readers.
7.3 Dining Philosophers Problem
Scenario: 5 philosophers sit around a circular table. Between each pair of philosophers is ONE chopstick (5 total). A philosopher alternates between thinking and eating. To eat, a philosopher needs BOTH chopsticks (left and right).
๐ฝ๏ธ Dining Philosophers โ Naรฏve Solution (CAUSES DEADLOCK)
Pseudocode semaphore chopstick[5]; // All initialized to 1 // Philosopher i (0 to 4) do { // THINKING ... P(chopstick[i]); // Pick up LEFT chopstick P(chopstick[(i+1)%5]); // Pick up RIGHT chopstick // EATING ... V(chopstick[i]); // Put down LEFT V(chopstick[(i+1)%5]); // Put down RIGHT } while (TRUE);
โ ๏ธ DEADLOCK: If all 5 philosophers pick up their left chopstick simultaneously, all are holding one and waiting for the other โ circular wait!
Solutions to Avoid Deadlock:
| Solution | How It Works |
|---|---|
| 1. Allow at most 4 | Use a counting semaphore initialized to 4 โ at most 4 philosophers can attempt to eat |
| 2. Asymmetric | Odd philosophers pick left first, even pick right first โ breaks circular wait |
| 3. All-or-nothing | A philosopher picks up both chopsticks only if BOTH are available (inside a critical section) |
๐ฝ๏ธ Dining Philosophers โ Deadlock-Free (Asymmetric Solution)
Pseudocode semaphore chopstick[5]; // All initialized to 1 // Philosopher i do { // THINKING ... if (i % 2 == 0) { // Even: pick left first P(chopstick[i]); P(chopstick[(i+1)%5]); } else { // Odd: pick right first P(chopstick[(i+1)%5]); P(chopstick[i]); } // EATING ... V(chopstick[i]); V(chopstick[(i+1)%5]); } while (TRUE);
Why it works: By making at least one philosopher pick up chopsticks in opposite order, we break the circular wait condition.
8. Precedence Graph
A precedence graph is a directed acyclic graph (DAG) where each node represents a statement/task and edges represent execution order constraints. If there's an edge from S1 to S2, then S1 must finish before S2 starts.
๐ Precedence Graph โ Example
Given statements: S1, S2, S3, S4, S5, S6
Constraints: S1 โ S2, S1 โ S3, S2 โ S4, S3 โ S4, S4 โ S5, S4 โ S6
Diagram
S1
/ \
S2 S3
\ /
S4
/ \
S5 S6
Meaning: S2 and S3 can run in parallel (both only depend on S1). S4 must wait for BOTH S2 and S3. S5 and S6 can run in parallel after S4.
Implementation with semaphores:
Pseudocode semaphore a=0, b=0, c=0, d=0, e=0, f=0; P1: S1; V(a); V(b); P2: P(a); S2; V(c); P3: P(b); S3; V(d); P4: P(c); P(d); S4; V(e); V(f); P5: P(e); S5; P6: P(f); S6;
9. Threads โ Lightweight Processes
A thread is the smallest unit of CPU execution. A process can have multiple threads sharing the same code, data, and files, but each thread has its own program counter, register set, and stack.
Analogy: A process is like a restaurant. Threads are the waiters. All waiters share the same kitchen (memory), menu (code), and tables (files), but each waiter takes independent orders (has own PC and stack).
User-Level Threads vs Kernel-Level Threads
| Feature | User-Level Threads (ULT) | Kernel-Level Threads (KLT) |
|---|---|---|
| Managed by | Thread library in user space | Operating System kernel |
| Kernel awareness | Kernel doesn't know threads exist | Kernel manages each thread |
| Context switch | Fast (no kernel intervention) | Slower (kernel mode switch) |
| Blocking | If one thread blocks, ALL threads in process block | Only the blocking thread blocks; others continue |
| Multiprocessor | โ Cannot use multiple CPUs (kernel sees one process) | โ Can run on multiple CPUs in parallel |
| Creation cost | Very cheap (no system call) | More expensive (system call needed) |
| Example | Green threads (Java pre-1.2), GNU Pth | Linux pthreads, Windows threads |
Multithreading Models
Many-to-One Model
Diagram
User Threads: T1 T2 T3 T4
\ | | /
\ | | /
Kernel Thread: [K1]
Many user threads map to ONE kernel thread. Thread management in user space (fast), but one blocking call blocks ALL. Cannot exploit multiprocessors. Example: Solaris Green Threads.
One-to-One Model
Diagram
User Threads: T1 T2 T3 T4
| | | |
Kernel Threads: K1 K2 K3 K4
Each user thread maps to ONE kernel thread. More concurrency โ one thread blocking doesn't block others. Can use multiple CPUs. Overhead: Creating a user thread requires creating a kernel thread. Example: Linux pthreads, Windows threads.
Many-to-Many Model
Diagram
User Threads: T1 T2 T3 T4 T5
\ | X | /
\ |/ \| /
Kernel Threads: K1 K2 K3
Many user threads map to many (fewer or equal) kernel threads. Best of both worlds โ no limit on user threads, can exploit multiple CPUs. Example: Solaris (LWP), HP-UX.
Scheduler Activations
Scheduler activations are a communication mechanism between the kernel and the user-level thread library. The kernel provides an upcall to the thread library whenever a significant event occurs (thread blocks, thread unblocks, new processor allocated).
The user-level thread library uses a data structure called a lightweight process (LWP) as an intermediary. Each LWP is attached to a kernel thread. The thread library schedules user threads onto available LWPs.
Benefit: Combines the speed of user-level thread management with the kernel's ability to handle blocking I/O and exploit multiprocessors.
10. Pthreads โ POSIX Thread API
Pthreads (POSIX Threads) is the standard C API for creating and managing threads on UNIX/Linux systems. It follows the One-to-One model on Linux.
Key Pthreads Functions
| Function | Purpose |
|---|---|
pthread_create() | Create a new thread |
pthread_join() | Wait for a thread to finish (like wait() for processes) |
pthread_exit() | Terminate calling thread |
pthread_mutex_init() | Initialize a mutex lock |
pthread_mutex_lock() | Acquire the mutex (blocks if already locked) |
pthread_mutex_unlock() | Release the mutex |
๐งต Pthreads โ Basic Thread Creation Example
C #include <stdio.h> #include <pthread.h> void* print_hello(void* arg) { int id = *((int*)arg); printf("Hello from thread %d\n", id); pthread_exit(NULL); } int main() { pthread_t threads[3]; int ids[3] = {1, 2, 3}; for (int i = 0; i < 3; i++) pthread_create(&threads[i], NULL, print_hello, &ids[i]); for (int i = 0; i < 3; i++) pthread_join(threads[i], NULL); printf("All threads completed.\n"); return 0; }
Compile: gcc -o threads threads.c -lpthread
๐ Pthreads โ Mutex Example (Thread-Safe Counter)
C #include <stdio.h> #include <pthread.h> int counter = 0; pthread_mutex_t lock; void* increment(void* arg) { for (int i = 0; i < 100000; i++) { pthread_mutex_lock(&lock); counter++; // Critical section pthread_mutex_unlock(&lock); } return NULL; } int main() { pthread_t t1, t2; pthread_mutex_init(&lock, NULL); pthread_create(&t1, NULL, increment, NULL); pthread_create(&t2, NULL, increment, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("Counter = %d\n", counter); // Always 200000 with mutex! pthread_mutex_destroy(&lock); return 0; }
Without mutex: Counter would be less than 200000 due to race condition!
Learn by Doing โ 3-Tier Lab Structure
๐ข Tier 1 โ GUIDED TASK: POSIX Semaphore Producer-Consumer in C
Step 1: Create the Source File
Create a file named producer_consumer.c and enter the following code:
C #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <unistd.h> #define BUFFER_SIZE 5 #define NUM_ITEMS 10 int buffer[BUFFER_SIZE]; int in = 0, out = 0; sem_t empty_slots; // Counts empty slots sem_t full_slots; // Counts filled slots pthread_mutex_t mutex; void* producer(void* arg) { for (int i = 1; i <= NUM_ITEMS; i++) { sem_wait(&empty_slots); // P(empty) โ wait for empty slot pthread_mutex_lock(&mutex); // P(mutex) โ lock buffer buffer[in] = i; printf("Producer: produced item %d at index %d\n", i, in); in = (in + 1) % BUFFER_SIZE; pthread_mutex_unlock(&mutex); // V(mutex) sem_post(&full_slots); // V(full) โ signal item available usleep(100000); // 100ms delay to visualise } return NULL; } void* consumer(void* arg) { for (int i = 1; i <= NUM_ITEMS; i++) { sem_wait(&full_slots); // P(full) โ wait for item pthread_mutex_lock(&mutex); // P(mutex) int item = buffer[out]; printf("Consumer: consumed item %d from index %d\n", item, out); out = (out + 1) % BUFFER_SIZE; pthread_mutex_unlock(&mutex); // V(mutex) sem_post(&empty_slots); // V(empty) โ signal slot freed usleep(150000); // 150ms delay } return NULL; } int main() { sem_init(&empty_slots, 0, BUFFER_SIZE); sem_init(&full_slots, 0, 0); pthread_mutex_init(&mutex, NULL); pthread_t prod_thread, cons_thread; pthread_create(&prod_thread, NULL, producer, NULL); pthread_create(&cons_thread, NULL, consumer, NULL); pthread_join(prod_thread, NULL); pthread_join(cons_thread, NULL); sem_destroy(&empty_slots); sem_destroy(&full_slots); pthread_mutex_destroy(&mutex); printf("\nAll items produced and consumed successfully!\n"); return 0; }
Step 2: Compile
Terminal
gcc -o producer_consumer producer_consumer.c -lpthread
Step 3: Run
Terminal
./producer_consumer
Step 4: Expected Output
๐ What to observe: The buffer index wraps around (circular buffer). Producer never overflows. Consumer never reads empty. This is synchronization at work!
๐ก Tier 2 โ SEMI-GUIDED: Race Condition Demo + Mutex Fix
Your Mission:
Demonstrate a race condition by having two threads increment a shared counter 1,000,000 times each WITHOUT a lock. Then fix it with a mutex.
Hints:
- Step 1: Create
race_demo.c. Declare a globalint counter = 0; - Step 2: Write a function
void* increment(void* arg)that loops 1,000,000 times doingcounter++ - Step 3: In
main(), create 2 threads runningincrement, join both, then print counter - Run multiple times: You'll see counter is NOT 2,000,000! It varies each run (race condition!)
- Fix: Add
pthread_mutex_t lock, wrapcounter++withpthread_mutex_lock()andpthread_mutex_unlock() - Run again: Counter is now exactly 2,000,000 every time โ
clock_gettime(). How much slower is the mutex version? Why? Is there a way to reduce lock contention?
๐ด Tier 3 โ OPEN CHALLENGE: Dining Philosophers with Deadlock Detection
The Brief:
Implement the full Dining Philosophers problem in C with pthreads. Requirements:
- 5 philosopher threads, 5 chopstick mutexes
- Implement the naรฏve solution first (left then right) โ observe deadlock
- Add deadlock detection: a monitor thread that periodically checks if all philosophers are holding one chopstick and waiting for another
- Implement the asymmetric solution (even/odd) to prevent deadlock
- Print state transitions: THINKING โ HUNGRY โ EATING
- Run for 30 seconds and report statistics: how many times each philosopher ate
Deliverable: Two C files โ dining_deadlock.c (with deadlock) and dining_safe.c (deadlock-free). Compare outputs.
Industry Spotlight โ A Day in the Life
๐ฉโ๐ป Aisha Begum, 28 โ Backend Engineer at Razorpay, Bangalore
Background: B.Tech CSE from JNTU Hyderabad. Self-taught competitive programming in 2nd year. Interned at a Hyderabad startup building payment APIs. Got placed at Razorpay through an off-campus drive after solving concurrency-related system design questions.
A Typical Day:
9:30 AM โ Morning standup with the Payments Platform team. Review overnight alerts โ any payment failures due to race conditions or timeouts?
10:00 AM โ Investigate a bug: two merchants received the same transaction ID. Root cause? A UUID generation function wasn't thread-safe. Fix: wrap it with a mutex.
11:30 AM โ Code review for a junior developer's implementation of a rate limiter. "Your counter increment isn't atomic โ use CAS or a semaphore here."
1:00 PM โ Lunch at Razorpay's cafeteria. Discuss the upcoming migration from mutex-based locks to lock-free queues for the payment processing pipeline.
2:00 PM โ Write a Go implementation of a concurrent worker pool using channels (Go's version of monitors). Each worker processes payment webhooks.
4:00 PM โ Load testing: simulate 10,000 concurrent payment requests. Check for race conditions using Go's race detector (go run -race).
5:30 PM โ Write documentation for the team's concurrency best practices guide.
| Detail | Info |
|---|---|
| Tools Used Daily | Go, Redis (distributed locks), PostgreSQL, Kafka, Docker, Git |
| Entry Salary (2024) | โน8โ12 LPA + ESOPs |
| Mid-Level (3โ5 yrs) | โน15โ25 LPA |
| Senior (7+ yrs) | โน30โ55 LPA |
| Companies Hiring | Razorpay, PhonePe, Paytm, Zerodha, CRED, Flipkart, Amazon, Google, Atlassian, Swiggy |
Earn With It โ Concurrency Bug Detection Consulting
๐ฐ Your Earning Path After This Chapter
Portfolio Piece: "Thread-Safe Producer-Consumer System in C" + "Dining Philosophers with Deadlock Detection" โ working GitHub repos with README, compilation instructions, and output screenshots.
Freelance & Consulting Gig Ideas:
โข Concurrency bug detection in existing codebases โ โน5,000โโน15,000/project
โข Thread-safe API wrapper development for startups โ โน8,000โโน20,000
โข Code review for race conditions in fintech applications โ โน3,000โโน10,000/review
โข OS/Systems Programming tutoring for GATE/placement students โ โน500โโน1,000/hour
| Platform | Best For | Typical Rate |
|---|---|---|
| Topmate | 1-on-1 OS/Systems mentoring | โน500โโน2,000/session |
| Upwork | Concurrency bug fixing, C/Go projects | $20โ$60/hour |
| GitHub Sponsors | Open-source sync libraries | Variable |
| Internshala | Systems programming internships | โน5,000โโน15,000/month |
| College Network | GATE OS tutoring | โน500โโน1,500/hour |
โฑ๏ธ Time to First Earning: 2โ4 weeks (complete labs, push to GitHub, start tutoring GATE aspirants on concurrency topics)
MCQ Assessment Bank โ 30 Questions (Bloom's Mapped)
Remember / Identify (Q1โQ5)
Which of the following is NOT a requirement of the critical section problem?
- Mutual Exclusion
- Progress
- Starvation Freedom
- Bounded Waiting
In Peterson's Algorithm, what is the purpose of the turn variable?
- To count the number of processes
- To act as a tie-breaker when both processes want to enter
- To store the process ID
- To measure execution time
turn variable breaks ties when both processes set their flags to true simultaneously. Whichever process set turn last gives priority to the other.A binary semaphore can take values:
- 0 and 1 only
- Any positive integer
- -1 and 1
- 0 to N
The P() operation on a semaphore is also known as:
- signal()
- wait()
- notify()
- release()
In the Dining Philosophers problem, how many chopsticks are shared among 5 philosophers?
- 10
- 5
- 4
- 3
Understand / Explain (Q6โQ10)
Why does the naรฏve Dining Philosophers solution cause deadlock?
- Because philosophers eat too fast
- Because all philosophers may pick up their left chopstick simultaneously, creating circular wait
- Because there aren't enough chopsticks
- Because the semaphores are initialized incorrectly
What is the key difference between a monitor's signal() and a semaphore's V()?
- signal() increments a counter; V() does not
- V() always increments the semaphore; signal() has no effect if no process is waiting
- They are identical operations
- signal() can wake multiple processes; V() wakes only one
In the Reader-Writer problem with reader preference, what can happen to writers?
- Writers get priority over readers
- Writers may starve if readers keep arriving continuously
- Writers and readers get equal access
- Writers can always interrupt readers
Why can't user-level threads take advantage of multiple CPU cores?
- User-level threads are too slow
- The kernel sees the entire process as a single schedulable unit
- User-level threads don't support mutexes
- User-level threads can only run on core 0
What is the purpose of the mutex semaphore in the Producer-Consumer solution?
- To count the number of items in the buffer
- To ensure mutual exclusion when accessing the shared buffer
- To track which slot is empty
- To wake up sleeping processes
Apply / Solve (Q11โQ15)
In Peterson's Algorithm, if process Pโ sets flag[0]=true and turn=1, and Pโ has flag[1]=false, what happens?
- Pโ enters the critical section immediately
- Pโ waits indefinitely
- Both processes enter the critical section
- Deadlock occurs
flag[1] && turn==1. Since flag[1] is false, the condition is false, and Pโ enters immediately. Pโ doesn't want to enter.A counting semaphore is initialized to 3. After the sequence P(), P(), P(), V(), P(), what is the semaphore value?
- 0
- 1
- -1
- 2
In the bounded buffer problem with buffer size 5, if 5 items are produced and 2 are consumed, what are the values of semaphores empty and full?
- empty=2, full=3
- empty=0, full=5
- empty=3, full=2
- empty=5, full=0
What is the output of test_and_set(&lock) when lock is currently false?
- Returns true, lock remains false
- Returns false, lock becomes true
- Returns true, lock becomes true
- Returns false, lock remains false
In the Reader-Writer solution, when the 1st reader calls P(rw_mutex), what happens to incoming writers?
- Writers can still enter
- Writers are blocked until all readers finish
- Writers terminate
- Nothing โ rw_mutex is not affected
Analyze / Compare (Q16โQ20)
Two threads execute counter++ 100 times each on a shared variable counter=0. What is the MINIMUM possible value of counter?
- 200
- 100
- 2
- 0
Which synchronization primitive uses busy waiting?
- Semaphore (with blocking)
- Monitor
- Test-and-Set spinlock
- Condition variable
What happens if a producer calls P(mutex) before P(empty) in the bounded buffer problem and the buffer is full?
- The producer waits efficiently
- Deadlock โ producer holds mutex and blocks on empty; consumer needs mutex to free a slot
- The producer overwrites the buffer
- Nothing unusual happens
Compare: In Many-to-One threading model, what happens when one thread makes a blocking system call?
- Only that thread blocks
- All threads in the process block
- The kernel creates a new thread
- The system call is converted to non-blocking
Analyzing the Bakery Algorithm: if two processes get the same ticket number, how is the tie resolved?
- Random selection
- The process with the smaller process ID goes first
- Both enter the critical section
- Both are denied entry
(number[j], j) < (number[i], i) uses process ID as a tie-breaker. The process with the smaller ID (lower j) gets priority when ticket numbers are equal.Evaluate / Judge (Q21โQ25)
For a payment gateway processing 10,000 concurrent transactions, which is the BEST synchronization approach?
- Peterson's Algorithm
- Disabling interrupts
- Mutex locks with lock-free queues for high-throughput paths
- Test-and-Set spinlocks on all operations
A system needs multiple readers but exclusive writers on a shared database. Which solution risks writer starvation?
- Writer-preference Reader-Writer
- Reader-preference Reader-Writer
- Binary semaphore for all operations
- Monitor with fair scheduling
Which threading model would you recommend for a web server handling thousands of concurrent connections on a multi-core machine?
- Many-to-One
- One-to-One
- Many-to-Many
- Single-threaded event loop only
Evaluate: Is Peterson's Algorithm suitable for modern multicore x86 processors without modifications?
- Yes, it works perfectly on all hardware
- No, because out-of-order execution and CPU cache coherency can violate its assumptions
- Yes, but only on Intel processors
- No, because it requires more than 2 processes
A startup asks: "Should we use spinlocks or blocking mutexes for protecting a shared counter updated every 10 microseconds?" What's your advice?
- Spinlocks โ the critical section is very short, so spinning is cheaper than context switching
- Blocking mutexes โ always better than spinlocks
- Disable interrupts โ simplest approach
- No synchronization needed โ 10ฮผs is too fast for conflicts
Create / Design (Q26โQ30)
You're designing a thread-safe queue for a message broker. Which combination of synchronization primitives would you use?
- One binary semaphore only
- Mutex for buffer access + counting semaphore for item count + counting semaphore for free space
- Two Peterson's Algorithms
- Global interrupt disable
To implement a precedence constraint where S3 must execute after both S1 and S2, which semaphore setup is correct?
- One semaphore initialized to 2; S1 does V(), S2 does V(), S3 does P() twice
- One semaphore initialized to 0; S1 does V(), S3 does P()
- Two semaphores a=0, b=0; S1 does V(a), S2 does V(b), S3 does P(a) then P(b)
- No semaphores needed
You need to design a system where exactly 3 threads can access a resource simultaneously. Which construct do you use?
- Binary semaphore
- Counting semaphore initialized to 3
- 3 separate mutex locks
- Peterson's Algorithm
Design question: In a ride-sharing app, multiple users request the same driver simultaneously. How would you ensure only ONE user gets matched?
- Use a counting semaphore initialized to the number of drivers
- Use Compare-and-Swap (CAS) on the driver's availability flag
- Let all users book and cancel extras later
- Use a Many-to-One threading model
You're building an IRCTC-like system where 500 users try to book 1 seat. Design the synchronization:
- Use a monitor with a condition variable: check seat count, if > 0 decrement and confirm, else wait
- Use 500 separate mutex locks
- No synchronization โ first-come-first-served is automatic
- Use Peterson's Algorithm for all 500 users
Short Answer Questions (5)
๐ Q1: Explain the three requirements of the Critical Section Problem with a real-world analogy.
Answer:
1. Mutual Exclusion: Only one process can be in its critical section at a time. Analogy: Only one person can use the ATM booth โ the door locks from inside.
2. Progress: If no process is in the critical section and some want to enter, the decision of who enters cannot be postponed indefinitely. Only processes not in their remainder section participate in the decision. Analogy: If the ATM is free and people are waiting, one of them must be allowed in โ you can't keep the booth empty.
3. Bounded Waiting: There must be a bound on the number of times other processes can enter before a waiting process gets its turn. Analogy: In a bank queue with token numbers, you're guaranteed to be served after a finite number of customers โ nobody can keep cutting the line.
๐ Q2: Differentiate between binary semaphore and counting semaphore with examples.
Answer:
Binary Semaphore (Mutex): Takes values 0 or 1 only. Used for mutual exclusion โ ensuring one process at a time. Example: A single-key bathroom โ key is either with someone (0) or hanging on the hook (1).
Counting Semaphore: Takes values from 0 to N. Used to control access to a resource with multiple instances. Example: A parking lot with 50 spaces โ semaphore starts at 50, each car entering decrements it, each car leaving increments it. When 0, new cars must wait.
Key difference: Binary semaphore = one resource. Counting semaphore = N identical resources.
๐ Q3: Why does the naรฏve Dining Philosophers solution lead to deadlock? How can you fix it?
Answer:
Deadlock cause: Each philosopher picks up their left chopstick first. If all 5 do this simultaneously, each holds one chopstick and waits for the other โ creating a circular wait. All four conditions for deadlock are met: mutual exclusion (chopstick is exclusive), hold and wait (holding left, waiting for right), no preemption (can't force a philosopher to release), and circular wait (P0โP1โP2โP3โP4โP0).
Solutions:
- Asymmetric ordering: Even-numbered philosophers pick left first, odd pick right first โ breaks circular wait
- Limit to 4: Allow at most 4 philosophers to sit simultaneously (counting semaphore = 4)
- All-or-nothing: Pick up both chopsticks only if both are available, inside a critical section
๐ Q4: Compare user-level threads and kernel-level threads. When would you choose each?
Answer:
User-Level Threads: Managed by a thread library in user space. Faster context switching (no kernel involvement), portable across OSes. BUT: if one thread blocks, all block; cannot use multiple CPUs.
Kernel-Level Threads: Managed by the OS kernel. Can exploit multiple CPUs, one blocking thread doesn't block others. BUT: slower creation and context switching due to system calls.
Choose ULT when: You need many lightweight threads with fast switching and the application is CPU-bound (no blocking I/O). Example: cooperative multitasking in game engines.
Choose KLT when: You need true parallelism on multicore CPUs, or your application does blocking I/O (network servers, file processing). Example: Web servers handling concurrent HTTP requests.
๐ Q5: What is a precedence graph? Draw one for: S1โS2, S1โS3, S2โS4, S3โS4. Implement with semaphores.
Answer:
A precedence graph is a DAG (Directed Acyclic Graph) showing execution order constraints between statements. An edge from Si to Sj means Si must complete before Sj begins.
Graph
S1
/ \
S2 S3
\ /
S4
S2 and S3 can execute in parallel (both depend only on S1). S4 must wait for both S2 and S3.
Semaphore implementation:
Pseudocode
semaphore a=0, b=0, c=0, d=0;
P1: S1; V(a); V(b);
P2: P(a); S2; V(c);
P3: P(b); S3; V(d);
P4: P(c); P(d); S4;
Case Studies (2)
๐ Case Study 1: BookMyShow โ The Race Condition That Oversold 200 Tickets
Background:
In 2019, during the release of Avengers: Endgame in India, BookMyShow experienced its highest-ever traffic โ 18 million searches per hour. Multiple users across cities were booking the last few seats in premium theatres simultaneously.
The Problem:
The booking system's "check availability โ deduct seat โ confirm" logic was not atomic. The code looked approximately like this:
Pseudocode (Buggy) book_seat(theatre_id, seat_id): available = db.query("SELECT available FROM seats WHERE id = ?", seat_id) if available > 0: db.update("UPDATE seats SET available = available - 1 WHERE id = ?", seat_id) return "BOOKING CONFIRMED" else: return "SOLD OUT"
Between the SELECT and UPDATE, another thread could read the same available count โ classic TOCTOU (Time-of-Check-to-Time-of-Use) race condition.
Impact:
~200 tickets were double-booked across 15 cities. Customers arrived at theatres to find their seats occupied. Result: โน3 lakh in refunds + compensation + massive social media backlash.
Solution Applied:
Pseudocode (Fixed) book_seat(theatre_id, seat_id): // Atomic operation โ UPDATE only succeeds if available > 0 rows_affected = db.update( "UPDATE seats SET available = available - 1 WHERE id = ? AND available > 0", seat_id ) if rows_affected == 1: return "BOOKING CONFIRMED" else: return "SOLD OUT"
Additionally, they implemented:
- Distributed locks (Redis-based mutex): For high-demand shows, a distributed lock prevents concurrent booking of the same seat
- Optimistic locking with version numbers: Each seat row has a version column; CAS-style update
- Queue-based booking: For Tatkal-style launches, users are placed in a virtual queue (counting semaphore pattern)
Discussion Questions:
- Why is the fixed SQL query better than wrapping the original code with a mutex?
- How is the virtual queue system similar to a counting semaphore?
- What would happen if BookMyShow used Peterson's Algorithm for synchronization?
๐ Case Study 2: Indian Railways Signal System โ Semaphores in Real Life
Background:
Indian Railways operates 13,000+ trains daily on 67,000+ km of track. Many sections are single-track where trains going in opposite directions share the same rail. This is literally the "single-lane bridge" analogy from the Critical Section Problem!
The Synchronization Challenge:
A single-track section between two stations is a critical section. Only one train can occupy it at a time. The railway signal system acts as a semaphore:
| Railway Concept | OS Concept |
|---|---|
| Single-track section | Critical Section (shared resource) |
| Green signal | P() succeeds โ semaphore > 0, enter CS |
| Red signal | P() blocks โ semaphore = 0, wait |
| Train exits section | V() โ signal, release the section |
| Token/Tablet system | Binary semaphore (mutex) |
| Double-track section | Counting semaphore (2 trains, one per direction) |
The Token System (Physical Mutex!):
In traditional Indian Railways, single-track sections use a physical token system. A metal token is kept at the station. A train driver MUST hold the token to enter the section. Only ONE token exists per section. When the train exits, the token is returned. This is a binary semaphore implemented in hardware โ invented before computers!
Modern Electronic Interlocking:
Today, most Indian Railway stations use Electronic Interlocking (EI) systems. These are computerised systems that implement the same logic digitally:
- Mutual exclusion: Only one train route set at a time per track section
- Progress: If the section is free, a waiting train is allowed immediately
- Bounded waiting: Priority rules ensure no train waits indefinitely
The 2023 Balasore Accident Connection:
The tragic Odisha train accident (June 2023, 296 deaths) was partly attributed to errors in the electronic interlocking system during maintenance. A signalling configuration error sent the Coromandel Express onto the wrong track. This is analogous to a race condition in the synchronization system โ the interlocking failed to ensure mutual exclusion on the track section.
Discussion Questions:
- How is the token system analogous to a binary semaphore? What is P() and V() in this context?
- If Indian Railways converted a single-track section to double-track, how would the semaphore value change?
- What synchronization failures could explain the Balasore accident from an OS perspective?
Chapter Summary
๐ง Key Takeaways โ Unit 4
- Race Condition: When multiple processes access shared data concurrently and the outcome depends on the order of execution
- Critical Section Problem: Requires Mutual Exclusion, Progress, and Bounded Waiting
- Peterson's Algorithm: Software solution for 2 processes using
flag[]+turn - Bakery Algorithm: Software solution for N processes using ticket numbers
- TAS & CAS: Hardware atomic instructions for building locks; TAS returns old value and sets to true; CAS only swaps if current value matches expected
- Semaphores: P() = wait/decrement; V() = signal/increment. Binary (0/1) for mutex, Counting (0โN) for resource pools
- Monitors: High-level construct with automatic mutual exclusion; condition variables with wait() and signal()
- Producer-Consumer: 3 semaphores โ mutex, empty, full. ALWAYS acquire counting semaphore before mutex!
- Reader-Writer: Reader preference may starve writers; writer preference may starve readers
- Dining Philosophers: Naรฏve solution deadlocks; fix with asymmetric ordering, limiting to N-1, or all-or-nothing
- Precedence Graphs: DAGs showing execution order; implemented with semaphores (one per edge)
- Threads: Lightweight processes sharing code/data/files. User-level (fast, no multicore) vs Kernel-level (slower, multicore)
- Threading Models: Many-to-One (no parallelism), One-to-One (true parallelism, overhead), Many-to-Many (best of both)
- Pthreads: POSIX API โ
pthread_create(),pthread_join(),pthread_mutex_lock/unlock()
Earning Checkpoint โ Skills Inventory After Unit 4
| Skill Acquired | Tool/Technique | Portfolio Deliverable | Earning-Ready? |
|---|---|---|---|
| Critical Section & Synchronization Theory | Conceptual | โ | โ Yes โ GATE/Interview ready |
| Semaphore Programming | POSIX semaphores in C | Producer-Consumer program | โ Yes โ systems programming roles |
| Mutex & Thread-Safe Code | Pthreads API | Race condition demo + fix | โ Yes โ backend engineering roles |
| Classical Problems | Dining Philosophers, Reader-Writer | Working implementations on GitHub | โ Yes โ interview differentiator |
| Concurrency Bug Detection | Code review, race analysis | Bug detection case studies | โ Yes โ โน5Kโโน15K/project consulting |
| OS Tutoring (GATE prep) | Synchronization concepts | Teaching notes + solved MCQs | โ Yes โ โน500โโน1,000/hour |
โ Unit 4 complete. MCQs: 30. Ready for Unit 5: Deadlock!
[QR: Link to EduArtha video tutorial โ Process Synchronization & Threads]