Semaphores in OS
Contents
Semaphores: Detailed Notes with Examples
1. Introduction to Semaphores
A semaphore is a synchronization mechanism used to control access to shared resources in a concurrent system. Introduced by Edsger Dijkstra, semaphores solve critical section problems and avoid race conditions. They maintain an internal counter and manage two atomic operations:
- Wait (P): Decrements the counter. If the counter becomes negative, the process blocks.
- Signal (V): Increments the counter. If there are blocked processes, one is unblocked.
2. Semaphore Operations
Pseudocode for Wait and Signal
// Wait (P) Operation
procedure wait(S):
S.count -= 1
if S.count < 0:
block the process and add it to S.queue
// Signal (V) Operation
procedure signal(S):
S.count += 1
if S.count <= 0:
unblock a process from S.queue
3. Types of Semaphores
1. Binary Semaphore (Mutex)
- Values: 0 (locked) or 1 (unlocked).
- Used for mutual exclusion (e.g., protecting critical sections).
- Ex: Controlling access to a shared variable.
Pseudocode for Binary Semaphore
binary_semaphore mutex = 1
// Process 1
wait(mutex)
// Critical Section
signal(mutex)
// Process 2
wait(mutex)
// Critical Section
signal(mutex)
2. Counting Semaphore
- Values: Non-negative integers.
- Used when multiple instances of a resource are available (e.g., a buffer with N slots).
- Ex: Producer-Consumer problem with a buffer of size 5.
Pseudocode for Counting Semaphore
counting_semaphore empty = 5 // Initially, all slots are empty
counting_semaphore full = 0 // No slots are full
binary_semaphore mutex = 1 // For mutual exclusion
// Producer
while true:
produce item
wait(empty)
wait(mutex)
add item to buffer
signal(mutex)
signal(full)
// Consumer
while true:
wait(full)
wait(mutex)
remove item from buffer
signal(mutex)
signal(empty)
consume item
4. Classic Problems with Semaphores
1. Producer-Consumer Problem
- Problem: Producers add items to a buffer, and consumers remove them. Ensure no overflow/underflow and mutual exclusion.
- Solution:
- Use
empty
(counting semaphore for empty slots). - Use
full
(counting semaphore for filled slots). - Use
mutex
(binary semaphore for buffer access).
- Use
C Code Example (POSIX Semaphores)
#include <semaphore.h>
#include <stdio.h>
#include <pthread.h>
#define BUFFER_SIZE 5
sem_t empty, full, mutex;
int buffer[BUFFER_SIZE];
int in = 0, out = 0;
void *producer(void *arg) {
for (int i = 0; i < 10; i++) {
sem_wait(&empty);
sem_wait(&mutex);
buffer[in] = i;
printf("Produced: %d\n", buffer[in]);
in = (in + 1) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&full);
}
return NULL;
}
void *consumer(void *arg) {
for (int i = 0; i < 10; i++) {
sem_wait(&full);
sem_wait(&mutex);
int item = buffer[out];
printf("Consumed: %d\n", item);
out = (out + 1) % BUFFER_SIZE;
sem_post(&mutex);
sem_post(&empty);
}
return NULL;
}
int main() {
pthread_t prod, cons;
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
sem_init(&mutex, 0, 1);
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
sem_destroy(&empty);
sem_destroy(&full);
sem_destroy(&mutex);
return 0;
}
2. Readers-Writers Problem
- Problem: Multiple readers can read simultaneously, but writers need exclusive access.
- Solution:
- Use
rw_mutex
(binary semaphore for writers). - Use
mutex
(binary semaphore to protect reader count). - Track
read_count
(number of active readers).
- Use
C Code Example
#include <semaphore.h>
#include <stdio.h>
#include <pthread.h>
sem_t rw_mutex, mutex;
int read_count = 0;
void *reader(void *arg) {
sem_wait(&mutex);
read_count++;
if (read_count == 1) {
sem_wait(&rw_mutex);
}
sem_post(&mutex);
printf("Reading...\n");
sem_wait(&mutex);
read_count--;
if (read_count == 0) {
sem_post(&rw_mutex);
}
sem_post(&mutex);
return NULL;
}
void *writer(void *arg) {
sem_wait(&rw_mutex);
printf("Writing...\n");
sem_post(&rw_mutex);
return NULL;
}
int main() {
pthread_t r1, r2, w1;
sem_init(&rw_mutex, 0, 1);
sem_init(&mutex, 0, 1);
pthread_create(&r1, NULL, reader, NULL);
pthread_create(&w1, NULL, writer, NULL);
pthread_create(&r2, NULL, reader, NULL);
pthread_join(r1, NULL);
pthread_join(w1, NULL);
pthread_join(r2, NULL);
sem_destroy(&rw_mutex);
sem_destroy(&mutex);
return 0;
}
5. Common Issues with Semaphores
- Deadlock: Occurs when processes wait indefinitely (e.g., incorrect order of
wait
calls). - Starvation: Processes might never get access (e.g., writers in the readers-writers problem).
- Priority Inversion: Low-priority processes hold semaphores needed by high-priority ones.
6. Summary
- Binary Semaphores: Mutual exclusion (mutex locks).
- Counting Semaphores: Resource management (e.g., buffer slots).
- Use Cases: Producer-consumer, readers-writers, and thread synchronization.
By carefully coordinating wait
and signal
operations, semaphores ensure safe access to shared resources in concurrent systems.