Operating System - Counting Semaphores



Semaphores are synchronization techniques used in operating systems to control access to shared resources by multiple processes. A counting semaphore is a type of semaphore used to manage access to a resource that has multiple instances. Read this chapter to understand the concept of counting semaphores, their operations, working, and implementation in operating systems.

Counting Semaphores

A counting semaphore is a synchronization mechanism that allows multiple processes to access multiple instances of a shared resource. It maintains a count that represents the number of available instances of the resource. When a process wants to access the resource, it performs a "wait" operation, which decrements the count. And when a process releases the resource, it performs a "signal" operation, which increments the count.

A process can access the resource only if the count is greater than zero. If the count is zero, the process is blocked until a resource instance becomes available.

// Initialize counting semaphore with value 3 
S = 3;

// Wait Operation
wait(S) {
    S = S - 1;
    if (S < 0)
        block();
}

// Signal Operation
signal(S) {
    S = S + 1;
    if (S <= 0)
        unblock();
}

Working of Counting Semaphore

The working of a counting semaphore can be understood through the following steps −

State 1 − There are 3 instances of a resource available, so the semaphore count is initialized to 3.

State 2 − Four processes (P1, P2, P3, and P4) are started running in the system.

State 3 − Process P1 wants to access a resource instance, so it performs a wait operation. The semaphore count is decremented to 2, and P1 is granted access to the resource.

State 4 − Now both processes P2 and P3 want to access the same resource instance. They both perform wait operations, semaphore count is decremented to 0. Both P2 and P3 are granted access to the resource.

State 5 − Process P4 also wants to access the resource instance. It performs a wait operation, but since the semaphore count is 0, P4 is blocked and added to the waiting queue.

State 6 − P1 finishes using the resource and performs a signal operation. The semaphore count is incremented to 1, and P4 is unblocked and granted access to the resource.

The image below illustrates the above working of counting semaphore −

Working of Counting Semaphore

Implementation of Counting Semaphore

The counting semaphore can be implemented in various programming languages. Here we have implemented in Python/ C++/ Java −

import threading
import time

semaphore = threading.Semaphore(3)

def process(process_id):
    print(f"Process {process_id} is trying to access the resource.")
    semaphore.acquire()  # wait operation
    print(f"Process {process_id} has acquired the resource.")
    time.sleep(2)  # Simulate resource usage
    print(f"Process {process_id} is releasing the resource.")
    semaphore.release()  # signal operation
threads = []
for i in range(5):
    t = threading.Thread(target=process, args=(i,))
    threads.append(t)
    t.start()
for t in threads:
    t.join()

The output of the above code will be −

Process 0 is trying to access the resource.
Process 0 has acquired the resource.
Process 1 is trying to access the resource.
Process 1 has acquired the resource.
Process 2 is trying to access the resource.
Process 2 has acquired the resource.
Process 3 is trying to access the resource.
Process 4 is trying to access the resource.
Process 0 is releasing the resource.
...
#include <iostream>
#include <semaphore.h>
#include <thread>
#include <vector>
#include <chrono>

sem_t semaphore;
void process(int process_id) {
    std::cout << "Process " << process_id << " is trying to access the resource." << std::endl;
    sem_wait(&semaphore); // wait operation
    std::cout << "Process " << process_id << " has acquired the resource." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(2)); // Simulate resource usage
    std::cout << "Process " << process_id << " is releasing the resource." << std::endl;
    sem_post(&semaphore); // signal operation
}

int main() {
    sem_init(&semaphore, 0, 3); // Initialize counting semaphore with value 3
    std::vector<std::thread> threads;
    for (int i = 0; i < 5; i++) {
        threads.push_back(std::thread(process, i));
    }
    for (auto& t : threads) {
        t.join();
    }
    sem_destroy(&semaphore);
    return 0;
}

The output of the above code will be −

Process 0 is trying to access the resource.
Process 0 has acquired the resource.
Process 1 is trying to access the resource.
Process 1 has acquired the resource.
Process 2 is trying to access the resource.
Process 2 has acquired the resource.
Process 3 is trying to access the resource.
...
import java.util.concurrent.Semaphore;

public class CountingSemaphoreExample {
    static Semaphore semaphore = new Semaphore(3); // Counting semaphore with initial value 3

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            final int processId = i;
            new Thread(() -> process(processId)).start();
        }
    }

    static void process(int processId) {
        try {
            System.out.println("Process " + processId + " is trying to access the resource.");
            semaphore.acquire(); // wait operation
            System.out.println("Process " + processId + " has acquired the resource.");
            Thread.sleep(2000); // Simulate resource usage
            System.out.println("Process " + processId + " is releasing the resource.");
            semaphore.release(); // signal operation
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The output of the above code will be −

Process 0 is trying to access the resource.
Process 0 has acquired the resource.
Process 1 is trying to access the resource.
Process 1 has acquired the resource.
Process 2 is trying to access the resource.
Process 2 has acquired the resource.
...

Counting Semaphore vs Binary Semaphore

Binary semaphores is another type of semaphore that can take only two values, 0 and 1. It is used to implement mutual exclusion for a single resource between multiple processes. Here we tabulated the differences between binary semaphore and counting semaphore −

Aspect Binary Semaphore Counting Semaphore
Definition A semaphore that can take only the values 0 and 1 A semaphore that can take non-negative integer values
Value Range 0 or 1 0 to N (where N is a positive integer)
Use Case Mutual exclusion for a single resource Managing access to multiple instances of a resource
Operations wait() and signal() wait() and signal()
Blocking Behavior Blocks if the resource is unavailable (value is 0) Blocks if no resources are available (value is 0)

Conclusion

Counting semaphores are advanced synchronization mechanism used in operating systems to manage access to multiple instances of a shared resource. It is implemented using wait() and signal() operations, which decrement and increment the semaphore count respectively. If the value of the semaphore is zero, processes are blocked until a resource instance becomes available.

Advertisements