Process Management & Synchronization

Process

A process is essentially a program in execution. It’s the active entity, unlike a program file which is just a passive collection of instructions stored on a disk. When you double-click an application icon or run a command, the operating system creates a process.

To manage everything, the operating system gives each process its own resources, including:

  • CPU time for executing instructions.
  • A dedicated section of memory (its address space).
  • Access to I/O devices (like files, network connections, and printers).

Process States

A process moves through several stages during its life. These are called process states. The main ones are:

  • New: The process is being created.
  • Ready: The process is waiting to be assigned to a CPU to run. It has everything it needs except the processor itself.
  • Running: Instructions are being executed by the CPU.
  • Waiting (or Blocked): The process is waiting for some event to occur, such as the completion of an I/O operation or receiving a signal.
  • Terminated: The process has finished execution.

This flow ensures that the CPU is used efficiently, switching between processes that are ready to run while others are waiting for resources.


Process Control Block (PCB)

The operating system keeps track of each process using a data structure called the Process Control Block. Think of it as an ID card for the process. It contains all the vital information the OS needs, including:

  • Process State: The current state (ready, running, etc.).
  • Process ID (PID): A unique identification number.
  • Program Counter: The address of the next instruction to be executed.
  • CPU Registers: A snapshot of the processor’s state.
  • Memory Management Information: Details about the memory allocated to the process.
  • I/O Status Information: A list of I/O devices allocated to the process.

When the OS switches from one process to another (a “context switch”), it saves the state of the current process in its PCB and loads the state of the next process from its PCB.


Process vs. Thread

It’s common to confuse a process with a thread.

  • A process is an independent program with its own memory space. Think of it like a house. 🏠
  • A thread is the smallest unit of execution within a process. Multiple threads can exist within a single process and share its resources (like memory). Think of threads as the different people living and working inside the house, sharing the kitchen and rooms.

This multi-threaded approach allows an application to perform several tasks concurrently, making it more efficient. For example, in a word processor, one thread might handle your typing while another handles spell-checking in the background.



Threads

A thread is the smallest unit of execution within a process. Think of it as a lightweight process. A single process can have multiple threads running concurrently, all executing tasks simultaneously. 🏃‍♂️💨

Every process starts with at least one thread, often called the main thread. New threads can be created to perform specific tasks in the background or in parallel. Because all threads within a process share the same memory and resources, they can communicate and share data easily, making them very efficient for concurrent programming.

A modern web browser is a perfect example. One thread might render the webpage you’re seeing, another could be downloading an image, and a third might be playing a video—all happening within the same browser process.


Process vs. Thread

It’s crucial to understand the distinction between a process and a thread.

FeatureProcessThread
WeightHeavyweightLightweight
MemoryEach process has its own separate memory space.Threads share the memory space of their parent process.
Creation TimeTakes more time to create.Takes less time to create.
CommunicationCommunication between processes (Inter-Process Communication) is complex and slower.Communication between threads is simpler and faster as they share memory.
IsolationProcesses are isolated from each other. If one process crashes, it doesn’t affect others.Threads are not isolated. If one thread crashes, it can take down the entire process.

In short, a process is like a separate house 🏠, while threads are the different people living and working inside that house 👨‍👩‍👧‍👦, sharing its rooms and resources.


Types of Threads

Threads are managed in two primary ways:

  1. User-Level Threads: These threads are managed by a user-level library without any kernel support. They are fast to create and manage because there’s no need to make a system call to the OS. However, if one user thread performs a blocking operation (like waiting for I/O), the entire process gets blocked.
  2. Kernel-Level Threads: These threads are managed directly by the operating system. The kernel handles thread creation, scheduling, and management. This is slower than managing user threads, but it has a significant advantage: if one thread gets blocked, the kernel can schedule another thread from the same process to run. Modern operating systems like Windows and Linux primarily use kernel-level threads.

Benefits of Multithreading

Using multiple threads in an application offers several advantages:

  • Responsiveness: It allows an interactive application to remain responsive even if a part of it is blocked or performing a lengthy operation.
  • Resource Sharing: Threads share memory and resources by default, which is more efficient than the complex mechanisms required for processes to share information.
  • Efficiency: Creating and switching between threads is much faster and less resource-intensive than creating and switching between processes.
  • Scalability: Multithreading is essential for taking advantage of multi-core processors, as different threads can run in parallel on different cores, significantly improving performance.


CPU Scheduling

CPU scheduling is the process the operating system uses to decide which of the processes in the ready queue should be allocated to the CPU for execution. The goal is to make the system efficient, fast, and fair. 🧠

Think of a busy restaurant kitchen with one head chef (the CPU) and many orders (the processes) waiting to be cooked. The kitchen manager (the scheduler) must decide the order in which the chef will prepare the dishes. This decision-making is CPU scheduling.


Preemptive vs. Non-Preemptive Scheduling

Scheduling can be broadly divided into two types:

  • Non-Preemptive: Once the CPU has been allocated to a process, it keeps the CPU until it either finishes or switches to a waiting state (e.g., for an I/O operation). The process cannot be interrupted. It’s like a chef who finishes one entire dish before even looking at the next order.
  • Preemptive: The CPU can be taken away from a process even if it has not finished. This is usually done when a higher-priority process arrives. This is like a chef stopping the preparation of a regular dish to handle a high-priority order from a VIP customer.

Common CPU Scheduling Algorithms

Here are the most important scheduling algorithms you’ll encounter in competitive exams:

1. First-Come, First-Served (FCFS)

This is the simplest scheduling algorithm. The process that requests the CPU first gets the CPU first. It’s like a queue at a ticket counter. 🎟️

  • Type: Non-Preemptive
  • How it works: Processes are executed in the order they arrive in the ready queue.
  • Advantage: Very simple to understand and implement.
  • Disadvantage: The average waiting time can be very long, especially if a short process gets stuck behind a long one (this is called the convoy effect).

2. Shortest Job First (SJF)

This algorithm selects the process with the smallest execution time to run next.

  • Type: Can be both Preemptive and Non-Preemptive.
    • Non-Preemptive SJF: Once a process starts, it runs to completion.
    • Preemptive SJF (also called Shortest Remaining Time First – SRTF): If a new process arrives with a shorter execution time than the remaining time of the current process, the CPU is preempted and given to the new, shorter process.
  • How it works: The scheduler looks at the length of the next CPU burst for all ready processes and chooses the shortest one.
  • Advantage: It’s provably optimal in terms of providing the minimum average waiting time.
  • Disadvantage: It’s impossible to know the exact length of the next CPU burst in advance, so it’s mainly used for long-term scheduling.

3. Priority Scheduling

In this algorithm, a priority is associated with each process, and the CPU is allocated to the process with the highest priority.

  • Type: Can be both Preemptive and Non-Preemptive.
  • How it works: The scheduler selects the process with the highest priority. Processes with equal priority are typically scheduled in FCFS order.
  • Advantage: Important processes can be given preferential treatment.
  • Disadvantage: Can lead to starvation, where low-priority processes may never get to run. This can be solved by aging, where the priority of a process is gradually increased the longer it waits.

4. Round Robin (RR)

This algorithm is specifically designed for time-sharing systems and is preemptive.

  • Type: Preemptive
  • How it works: A small unit of time, called a time quantum or time slice (e.g., 10-100 milliseconds), is defined. The ready queue is treated as a circular queue. The scheduler goes around this queue, allocating the CPU to each process for a time quantum. If the process is still running after its time slice, it’s preempted and put at the back of the queue. 🔄
  • Advantage: It’s fair, as every process gets an equal share of the CPU. It provides a good response time.
  • Disadvantage: Performance depends heavily on the size of the time quantum. A very small quantum causes frequent context switches (high overhead), while a very large quantum makes it behave like FCFS.


Process Synchronization

Process synchronization is the task of coordinating the execution of multiple processes so that they can share resources or data without causing inconsistencies. When multiple processes access the same data concurrently, the outcome can depend on the specific order in which they run, potentially leading to incorrect results. Synchronization mechanisms are used to enforce mutual exclusion, ensuring that only one process can access a shared resource at a time. 🤝


The Race Condition and The Critical Section Problem

The core issue that synchronization addresses is the race condition. This occurs when multiple processes or threads access and manipulate the same shared data, and the final result depends on which one “wins the race” and finishes last.

Consider a simple example: two processes, P1 and P2, both need to update a shared bank account balance of ₹10,000.

  • P1 reads the balance (₹10,000) and wants to deposit ₹2,000.
  • P2 reads the balance (₹10,000) and wants to deposit ₹5,000.

If P1 adds its deposit and writes back ₹12,000, and then P2 adds its deposit to the original value it read and writes back ₹15,000, the final balance will be incorrect. The ₹2,000 from P1 is lost. This is a race condition. 🏁

To prevent this, we need to protect the part of the code that accesses the shared resource (the account balance). This protected section of code is known as the critical section. The critical section problem is to design a protocol that processes can use to cooperate. A solution must satisfy three requirements:

  1. Mutual Exclusion: Only one process can be executing in its critical section at any given time.
  2. Progress: If no process is in its critical section and some processes want to enter, only those processes that are not in their remainder sections can participate in the decision of which will enter its critical section next, and this selection cannot be postponed indefinitely.
  3. Bounced Waiting: There must be a limit on the number of times that other processes are allowed to enter their critical sections after a process has made a request to enter its critical section and before that request is granted.

Solutions to the Critical Section Problem

Several mechanisms are used to achieve process synchronization and solve the critical section problem.

1. Mutex Locks

A Mutex (short for Mutual Exclusion) is the simplest synchronization tool. It’s a lock that a process must acquire before entering its critical section and release when it exits.

  • It has two states: locked and unlocked.
  • A process wanting to enter the critical section tries to acquire the lock.
  • If the lock is unlocked, the process acquires it and enters the critical section.
  • If the lock is already held by another process, the requesting process is blocked until the lock is released.

Think of it like a key to a single restroom. 🔑 Only one person can have the key and use the restroom at a time. Anyone else who needs it has to wait until the key is returned.

2. Semaphores

A semaphore is a more versatile synchronization tool. It is an integer variable that, apart from initialization, is accessed only through two standard atomic operations: wait() and signal().

  • wait() operation: Decrements the semaphore value. If the value becomes negative, the process calling wait() is blocked.
  • signal() operation: Increments the semaphore value. If the value is not positive, a process blocked by a wait() call is unblocked.

There are two main types of semaphores:

  • Binary Semaphore: The value can only be 0 or 1. It functions essentially like a mutex lock.
  • Counting Semaphore: The value can range over an unrestricted domain. It can be used to control access to a resource that has multiple instances (e.g., a pool of database connections). The initial value of the semaphore is the number of available resources.

Think of a counting semaphore like a system for renting bicycles. If a bike shop has 5 bikes (the semaphore value is 5), the wait() operation is like taking a bike. When you take one, the count goes down. If the count is 0, you have to wait. The signal() operation is like returning a bike, which increments the count and allows someone who was waiting to take one.



Deadlocks

A deadlock is a situation where two or more processes are stuck in a state of indefinite waiting. Each process is holding onto a resource that another process needs, and it’s waiting for a resource held by another process in the same group. This creates a circular dependency where no process can proceed. 🛑

Imagine two people walking towards each other in a narrow hallway. They both stop, and each waits for the other to move aside. Neither moves, and they are stuck—that’s a deadlock.


The Four Necessary Conditions for Deadlock

For a deadlock to occur, four specific conditions must be met simultaneously. If even one of these conditions is not present, a deadlock cannot happen.

  1. Mutual Exclusion: At least one resource must be held in a non-sharable mode. This means only one process can use the resource at a time. If another process requests that resource, it must wait until the resource has been released. (Think of a printer—only one document can be printed at a time).
  2. Hold and Wait: A process must be holding at least one resource while simultaneously waiting for another resource that is currently held by another process.
  3. No Preemption: A resource cannot be forcibly taken away from a process holding it. The process must release the resource voluntarily after it has completed its task.
  4. Circular Wait: There must be a set of waiting processes {P0, P1, …, Pn} such that P0 is waiting for a resource held by P1, P1 is waiting for a resource held by P2, …, Pn is waiting for a resource held by P0. This creates the circular chain of dependencies. 🔄

Methods for Handling Deadlocks

There are three primary strategies that operating systems use to deal with deadlocks:

1. Deadlock Prevention

This is the most stringent approach. It involves designing the system to ensure that at least one of the four necessary conditions for a deadlock can never hold true. For example, the system might disallow the “Hold and Wait” condition by requiring a process to request all its needed resources at once before it can begin execution. While effective, this method can lead to low resource utilization and reduced system throughput.

2. Deadlock Avoidance

This strategy requires the operating system to have advance information about the resources a process will need for its entire lifetime. With this information, the OS can make decisions that ensure the system will never enter an unsafe state (a state that could potentially lead to a deadlock). The most famous algorithm for deadlock avoidance is the Banker’s Algorithm. This approach is more flexible than prevention but requires prior knowledge that is not always available.

3. Deadlock Detection and Recovery

This approach allows the system to enter a deadlocked state, then detects it and recovers from it. The system periodically runs an algorithm to check for a circular wait in the resource allocation graph. If a deadlock is detected, the system can recover by:

  • Process Termination: Aborting one or more of the deadlocked processes.
  • Resource Preemption: Forcibly taking a resource from one process and giving it to another. This often involves “rolling back” the preempted process to a safe state.

This is the approach taken by most general-purpose operating systems like Windows and Linux. They often assume that deadlocks are rare and that the overhead of prevention or avoidance is not worth it.