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:

The operating system manages all these tasks concurrently, giving the impression that they are running at the same time.

Why Concurrency is Needed

Types of Concurrency

  1. Process Concurrency
    • Multiple independent processes execute concurrently.
    • Each process has its own memory space.
  2. Thread Concurrency
    • Multiple threads within the same process execute concurrently.
    • Threads share the same memory and resources.

Issues in Concurrency

Concurrency introduces several challenges:

Synchronization Mechanisms

To solve concurrency problems, operating systems use:

**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

Example

counter++;      // Critical Section
balance -= 500; // Critical Section

Here:

Interview Tip

Shared Resource ≠ Critical Section


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


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.
--- ### One thing interviewers often ask **Is every critical section a race condition?** **Answer:** No.

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:

  1. Acquire the lock.
  2. Execute the critical section.
  3. 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


Real-Life Example

Imagine a room with one key.

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.

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.
--- ## When to Use Use a **Mutex** when only one thread should access a resource.

Examples:

Use a Semaphore when a limited number of threads can safely access a resource.

Examples:


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.