CST334 – Operating Systems - Week 6
This week, we are learning about semaphores to solve problems in concurrency. The semaphores are an alternative technique to the locks and condition variables using the two routines, sem_wait() and sem_post(). A semaphore initial value determines the routines' behavior (i.e., sem_init(&s, 0, 1;) indicates that the initial value of the semaphore is 1, and 0 indicates it is shared among other threads. sem_wait() decrement the value of semaphore when the value is above 0, and return. When the value is 0 or negative, sem_wait() simply waits till the value is above 0 again. sem_post() increment the value of semaphore and return. The negative value of semaphore is the count of the waiting threads.The use scenarios for semaphore: -
1- Used as a lock (binary semaphore)In this case, the semaphore is initialized as value 1, and the critical section is surrounded by sem_wait() and sem_post().
2- Ordering events (similar to condition variables)
In this case, a thread is waiting for a condition to come true. While another thread is working on the condition, a signal will wake it once the condition is satisfied. To accomplish this, the semaphore is initialized to the value of zero, the ordering thread calls sem_wait(), and the delivery thread calls sem_post().
3- The Producer/Consumer (Bounded Buffer) – one p and one c
The idea is that the producer waits for an empty buffer to fill it, and the consumer waits for a full buffer to use it. Again, the initial value of the semaphore determines the outcome. In this case, there are two semaphores initialized for full and empty to 0,1, respectively. If a consumer runs first calling a sem_wait(&full), the value becomes -1 and waits for a sem_post() to be called. On the other hand, the producer is calling sem wait(&empty) on an initial value of 1, which is decremented to zero and makes an entry to the buffer. Then sem_post(&full) is changed to 0. Now, the consumer is awake. The producer will continue to run, or the consumer will run to realize a full buffer and finish the program.
4- The Producer/Consumer (Bounded Buffer) – multi producers and consumers For multiple producers and consumers, a mutual exclusion must be implemented to avoid race condition and protect critical section. The use of binary semaphore surrounding the producer and consumer with sem_wait(&mutex), sem_post (&mutex), will result in a deadlock. Instead of calling sem_wait(&mutex) and sem_post(&mutex) before sem_wait() is called on full and empty, the lock is lowered as follow.
sem_wait(&empty);sem_wait(&mutex); //lockput(i);sem_post(&mutex); //unlocksem_post(&full);
Concurrency Problems
1- Atomicity-Violation Bugs: the absence of proper synchronization leads to this problem. When resources are shared between threads, the use of mutexes lock/unlock ensures that access to critical sections is done without a chance of interruption.2- Order-Violation Bugs: This is a non-deadlock bug caused by disordered memory access, which could result in unpredictable behavior. The solution to these types of bugs is to use conditional variables or semaphores to enforce ordering.
3- Deadlock Bugs: The problem occurs when threads hold locks and depend on each other for lock release. Circular dependencies may occur in large code if locking is not designed carefully. Four conditions require prevention to avoid deadlocks: mutual exclusion, hold-and-wait, no preemption, and circular wait.