Week 6 - Conditional Variables and Semaphores

This week we went deeper into the rabbit hole of concurrency amongst operating systems. The largest topics that were introduced were condition variables, the bounded buffer problem, and semaphores.

In multi threaded programs a thread will most likely wait for a condition to be met to continue its operations and one particular method is spinning where the thread will stay locked in a loop until it is able to retrieve a lock and continue. Though the problem with this method is that it wastes CPU cycles while it's spinning while other threads could have used the cycle to do other things. Another approach that helps relieve the waste of resources is with condition variables. The condition variable is a queue that a thread can put itself in when a condition is not met, and allows itself to be in a waiting condition or asleep. A way to wake up the thread is if a signal is called upon the condition variable. The condition variable can be declared with pthread_cond_t c = PTHREAD_COND_INITIALIZER, and in most cases a lock like pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; will still be involved. The thread can be put into a waiting state by calling pthread_cond_wait(c, mutex). When this is called the thread will release its lock and remain in a queue upon the condition variable, c. Only until a pthread_cond_signal(pthread_cond_t *c) on c will it continue. Good practice of using condition variables is to manage the lock around them. In the case of signaling it is best to hold the lock when calling a signal, and even for a wait. The benefits to using conditional variables is with better CPU utilization, thread safety, and maintaining easy to understand conditions. They are also a core topic for synchronization as it allows threads to manage access with shared resources.


A way that condition variables can be used is for the bounded buffer problem which involves having a producer and consumer. It allows threads to share data without complications of interference with the data. The buffer is created by a producer thread which creates the data and places it into a buffer, the consumer thread will take the data out of the buffer and use it. Utilization of locks and conditional variables help manage the flow of creating and taking data from the buffer and allows for safe coordination among threads. There are some important key elements to creating this buffer relationship: one is if the buffer is full then the producer must wait and not produce more data, if the buffer is empty the consumer must wait because if it doesn't it will consume nothing, and lastly to avoid race conditions a consumer or a producer should access the buffer one at a time. Conditional variables will help with this situation along with locks by allowing producers and consumers to wait/signal when a condition is met in either thread.


Lastly I will touch a bit upon semaphores as they are another method of synchronization that controls access to a shared resource. Semaphores are basically counting numbers, as a number will either be decremented when a sem_wait() is called and if the value is 0 the thread will block/wait until it is greater than 0; other case is an increment when a sem_post() is called which wakes up one of the waiting threads. The chapter that discusses semaphores provided some deeper understanding in implementing the read and write locks that were needed for the last homework assignment. It did take some time and practice to understand how incrementing and decrementing variables work. I was appreciative of learning about this topic as the idea locks and deadlocking were the most intriguing in my last course about database systems.



Comments