- OS - Home
- OS - Needs
- OS - Overview
- OS - History
- OS - Components
- OS - Structure
- OS - Architecture
- OS - Services
- OS - Properties
- Process Management
- Processes in Operating System
- States of a Process
- Process Schedulers
- Process Control Block
- Operations on Processes
- Inter Process Communication (IPC)
- Context Switching
- Multi-threading
- Scheduling Algorithms
- Process Scheduling
- Types of Scheduling
- Scheduling Algorithms Overview
- FCFS Scheduling Algorithm
- SJF Scheduling Algorithm
- Round Robin Scheduling Algorithm
- HRRN Scheduling Algorithm
- Priority Scheduling Algorithm
- Multilevel Queue Scheduling
- Lottery Scheduling Algorithm
- Starvation and Aging
- Turn Around Time & Waiting Time
- Burst Time in SJF Scheduling
- Process Synchronization
- Process Synchronization
- Solutions For Process Synchronization
- Hardware-Based Solution
- Software-Based Solution
- Critical Section Problem
- Critical Section Synchronization
- Mutual Exclusion Synchronization
- Peterson's Algorithm
- Dekker's Algorithm
- Bakery Algorithm
- Semaphores
- Binary Semaphores
- Counting Semaphores
- Mutex
- Turn Variable
- Bounded Buffer Problem
- Reader Writer Locks
- Test and Set Lock
- Monitors
- Sleep and Wake
- Race Condition
- OS Deadlock
- Introduction to Deadlock
- Conditions for Deadlock
- Deadlock Handling
- Deadlock Prevention
- Deadlock Avoidance (Banker's Algorithm)
- Deadlock Detection and Recovery
- Deadlock Ignorance
- Memory Management
- Memory Management
- Contiguous Memory Allocation
- Non-Contiguous Memory Allocation
- First Fit Algorithm
- Next Fit Algorithm
- Best Fit Algorithm
- Worst Fit Algorithm
- Fragmentation
- Virtual Memory
- Segmentation
- Buddy System
- Slab Allocation
- Overlays
- Paging and Page Replacement
- Paging
- Demand Paging
- Page Table
- Page Replacement Algorithms
- Optimal Page Replacement Algorithm
- Belady's Anomaly
- Thrashing
- Storage and File Management
- File Systems
- File Attributes
- Structures of Directory
- Linked Index Allocation
- Indexed Allocation
- Disk Scheduling Algorithms
- FCFS Disk Scheduling
- SSTF Disk Scheduling
- SCAN Disk Scheduling
- LOOK Disk Scheduling
- I/O Systems
- I/O Hardware
- I/O Software
- OS Types
- OS - Types
- OS - Batch Processing
- OS - Multiprocessing
- OS - Hybrid
- OS - Monolithic
- OS - Zephyr
- OS - Nix
- OS - Linux
- OS - Blackberry
- OS - Garuda
- OS - Tails
- OS - Clustered
- OS - Haiku
- OS - AIX
- OS - Solus
- OS - Tizen
- OS - Bharat
- OS - Fire
- OS - Bliss
- OS - VxWorks
- OS - Embedded
- OS - Single User
- Miscellaneous Topics
- OS - Security
- OS Questions Answers
- OS - Questions Answers
- OS Useful Resources
- OS - Quick Guide
- OS - Useful Resources
- OS - Discussion
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.
- What is Counting Semaphores?
- Working of Counting Semaphore
- Implementation of Counting Semaphor
- Counting Semaphore vs Binary Semaphore
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 −
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.