Concurrency is the ability of an operating system to execute multiple processes or threads simultaneously or in overlapping time periods.
On a single-core CPU, this is achieved by rapidly switching between tasks using context switching.
On a multi-core CPU, tasks can actually execute in parallel.
Example
Suppose you are:
- Downloading a file
- Listening to music
- Editing a document
The operating system manages all these tasks concurrently, giving the impression that they are running at the same time.
Why Concurrency is Needed
- Improves CPU utilization
- Increases system throughput
- Enhances responsiveness
- Allows resource sharing among multiple processes
- Supports multitasking
Types of Concurrency
- Process Concurrency
- Multiple independent processes execute concurrently.
- Each process has its own memory space.
- Thread Concurrency
- Multiple threads within the same process execute concurrently.
- Threads share the same memory and resources.
Issues in Concurrency
Concurrency introduces several challenges:
- Race Condition: Multiple threads/processes access shared data simultaneously, causing incorrect results.
- Deadlock: Two or more processes wait indefinitely for resources held by each other.
- Starvation: A process waits indefinitely because other processes continuously receive resources.
- Livelock: Processes keep changing their states in response to each other but make no progress.
Synchronization Mechanisms
To solve concurrency problems, operating systems use:
- Mutex (Mutual Exclusion)
- Semaphores
- Monitors
- Condition Variables
- Locks (Spinlocks, Read-Write Locks)
| **Advantages of Concurrency** | **Disadvantages of Concurrency** |
| Better CPU utilization by keeping the CPU busy. | Programming becomes more complex. |
| Improves system responsiveness (applications remain interactive). | Race conditions can occur when accessing shared data. |
| Increases overall system throughput. | Deadlocks may occur if resources are not managed properly. |
| Allows multiple tasks to progress at the same time. | Synchronization introduces additional overhead. |
| Efficient sharing of system resources. | Debugging and testing concurrent programs is difficult. |
| Supports multitasking and multi-user environments. | Starvation and livelock may occur in some scheduling scenarios. |
Diagram
CPU
|
-----------------
| | |
Process1 Process2 Process3
| | |
Runs concurrently
(via context switching or multiple cores)
Critical Section
A Critical Section is a part of a program where a shared resource is accessed or modified.
It should be executed by only one thread or process at a time to maintain data consistency.
Key Points
- It is a section of code, not the resource itself.
- It accesses or modifies a shared resource.
- If multiple threads/processes execute it simultaneously, it may lead to a race condition.
- Synchronization mechanisms like Mutex and Semaphore are used to protect critical sections.
Example
counter++; // Critical Section
balance -= 500; // Critical Section
Here:
counterandbalanceare shared resources.- The statements modifying them are critical sections.
Interview Tip
Shared Resource ≠ Critical Section
- Shared Resource → Data being shared (e.g.,
counter, file, database row). - Critical Section → Code that accesses or modifies that shared resource.
Race Condition
A Race Condition occurs when two or more threads/processes access and modify a shared resource at the same time, causing the final result to depend on the order in which they execute.
Key Points
- Occurs only when there is a shared resource.
- Usually happens inside a critical section.
- The output becomes unpredictable because it depends on thread scheduling.
- Can lead to data inconsistency.
- Prevented using synchronization mechanisms like Mutex and Semaphore.
Example
counter = 0
Thread A:
counter++;
Thread B:
counter++;
Expected Result:
counter = 2
Possible Result (Race Condition):
counter = 1
Because both threads read the same value before either writes the updated value.
Relationship with Critical Section
Shared Resource
↓
Critical Section
↓
Multiple threads enter together
↓
Race Condition
A race condition can occur only if multiple threads/processes access a critical section without proper synchronization.
Interview Tip
Critical Section vs Race Condition
| Critical Section | Race Condition |
| A section of code that accesses a shared resource. | A problem that occurs when multiple threads/processes execute a critical section simultaneously. |
| It is not an error by itself. | It leads to incorrect or inconsistent results. |
A critical section is just code that accesses shared data.
It only becomes a race condition if multiple threads/processes execute it concurrently without synchronization.
Everything we've learned so far leads to this question:
How do we prevent a race condition? The simplest answer is Mutex.
Mutex (Mutual Exclusion)
A Mutex (Mutual Exclusion) is a synchronization mechanism that allows only one thread or process to access a critical section at a time.
How it Works
Think of a mutex as a lock.
Before entering a critical section:
- Acquire the lock.
- Execute the critical section.
- Release the lock.
If another thread tries to enter while the lock is held, it waits until the lock is released.
Example
Without Mutex:
counter++;
If two threads execute this simultaneously, a race condition may occur.
With Mutex:
lock();
counter++;
unlock();
Now only one thread can execute counter++ at a time.
Key Points
- Prevents race conditions.
- Protects critical sections.
- Only one thread/process can hold the mutex at a time.
- Other threads/processes wait until the mutex is released.
Real-Life Example
Imagine a room with one key.
- Whoever has the key can enter the room.
- Everyone else must wait.
- When the person leaves, they return the key.
- The next person can then enter.
The key is the mutex.
Flow
Thread A
│
Acquire Mutex
│
Critical Section
│
Release Mutex
│
Thread B can now enter
Interview Tip
A mutex doesn't make code faster.
It makes the code safe by ensuring only one thread accesses the critical section at a time.
Sometimes using a mutex can even reduce performance because threads may spend time waiting for the lock.
One important interview question
Q: Can a race condition still happen if a mutex is used correctly? Answer: No.
If every access to the shared resource is protected by the same mutex and the mutex is used correctly (always locked before access and unlocked afterward), only one thread can execute the critical section at a time, so a race condition is prevented.
Semaphore
A Semaphore is a synchronization mechanism that controls access to a shared resource by allowing a fixed number of threads/processes to access it at the same time.
Unlike a mutex, which allows only one thread, a semaphore allows multiple threads depending on its count.
How it Works
A semaphore maintains a counter.
For example:
Semaphore = 3
This means only 3 threads can enter the critical section simultaneously.
When a thread enters:
Semaphore = 2
Another enters:
Semaphore = 1
Another enters:
Semaphore = 0
Now if a fourth thread arrives, it waits until one of the existing threads finishes and releases the semaphore.
Types of Semaphore
1. Binary Semaphore
Count = 1
Allows only one thread at a time.
It behaves similarly to a mutex, but it is not the same thing.
2. Counting Semaphore
Count = N
Allows N threads to access the resource simultaneously.
Example
Imagine a database connection pool with 10 connections.
Only 10 requests can use the database at the same time.
If the 11th request arrives, it waits until a connection becomes available.
This is a perfect use case for a counting semaphore.
Real-Life Example
Think of a parking lot with 50 parking spaces.
- 50 cars can park.
- The 51st car must wait.
- When a car leaves, another car can enter.
The available parking spaces are like the semaphore count.
Mutex vs Semaphore
| Mutex | Semaphore |
| Allows only **1** thread. | Allows **N** threads. |
| Used to protect a critical section. | Used to manage a limited number of shared resources. |
| Acts like a single key. | Acts like multiple keys. |
Examples:
- Updating a bank account balance
- Modifying a shared variable
- Writing to the same file
Use a Semaphore when a limited number of threads can safely access a resource.
Examples:
- Database connection pool
- Thread pool
- Printer pool
- API rate limiting (limited concurrent requests)
One interview question
Q: Why not always use a semaphore instead of a mutex? Answer:
Because some resources must never be accessed by more than one thread at a time.
For example:
balance = balance - 500;
Allowing multiple threads here could corrupt the balance.
A mutex is the correct choice.