OS TIRGUL 5 - Mutex and Semaphores

OS TIRGUL 5 - Mutex and Semaphores


Synchronizing threads

As we have seen last time, when two threads try to increment the same variable at the same time, they may leave the variable in an incorrect condition. This is true for every resource the threads share (memory variables, screen, modem etc.). The parts of the code that access the shared resource are called critical sections. To avoid using the same resource at the same time, a thread should perform some check to see whether there is another thread in the critical section. If there is no thread in the critical the thread may enter the critical section. Otherwise, it has to wait until the critical section is free again.

The mechanism that performs this check is called a mutex. A mutex has two states, that are usually referred to as unlocked and locked. An unlocked mutex indicates that the critical section is empty, and locked mutex indicates that there is some thread inside the critical section. A thread that wishes to access a resource checks the mutex associated with that resource. If the mutex is unlocked, it means there is no thread in the critical section, so the thread locks the mutex and enters the critical section. When the thread leaves the critical section it should unlock the mutex. If the mutex is locked, it means that there is another thread in the critical section, so the thread (that is trying to lock the mutex and enter) waits until the mutex becomes unlocked.

How mutex work. (and how they don't)

There are two operations defined on a mutex (beside initializing and destroying). The first is an operation that checks the state of the mutex, and then it locks the mutex if it is unlocked, or waits until it becomes unlocked. This operation is usually called lock. The second operation unlocks the mutex, and it usually is called unlock.

The first question that should arise is what happens when two threads call lock at the same time. Seemingly, they could ruin the mutex. However, notice that the operating system performs the lock (it is a function of the OS), and we know that the operating system is the one that decides when to schedule a new thread. Combining this information we get an elegant solution. When executing the lock function the operating system does not allow rescheduling. More specifically, the code of lock disables interrupts. This way two lock functions cannot ruin the mutex.

The second question that should arise is how the Os checks that a locked mutex was unlocked. Afterall, checking every some period of time is very not efficient. The answer is, it doesn't! The OS does not check whether a mutex is unlocked every some period of time! Every mutex has a list of threads that are waiting for it to unlock. When a thread discovers a mutex is locked, it adds itself to the waiting list. When the mutex is unlocked, the OS chooses one of the threads in the waiting list of that mutex, and wakes this thread. The thread then will try to lock the mutex again.

Syntax in Linux

Defining and initializing a mutex

A mutex is defined with the type pthread_mutex_t, and it needs to be assigned the initial value: "PTHREAD_MUTEX_INITIALIZER";

Locking a mutex

A mutex is locked with the function: pthread_mutex_lock(pthread_mutex_t *mutex)). This function gets a pointer to the mutex it is trying to lock. The function returns when the mutex is locked, or if an error occurred. (a locked mutex is not an error, if a mutex is locked the function waits until it is unlocked).

Unlocking a mutex

A mutex is unlocked with the function: pthread_mutex_unlock(pthread_mutex_t *mutex)).

Here is an example.

Semaphore

A semaphore is an extension of a mutex. A mutex allows one thread inside the critical section, a semaphore allows n threads in a critical section (when the number n is given as a parameter on the initialization). A semaphore is useful when a resource has more than one instance, and a mutex can be implemented by initializing a semaphore with the value 1.

There are two operations defined on a semaphore (beside initializing and destroying). The first is an operation that checks the value of the semaphore, and then it decreases it by 1 if it is positive, or waits until it becomes positive. This operation is usually called wait or decrease. The second operation increases the value of the semaphore, and it usually is called post or increase.

Syntax in Linux

Defining and initializing a semaphore

A semaphore is defined with the type sem_t, and it need to be initialized with the function sem_init.

Waiting on a semaphore

The value of a semaphore is decreased with the function: sem_wait. This function gets a pointer to the semaphore it is trying to decrease. The function returns when the semaphore's value is decreased, or if an error occurred. (a semaphore with value 0 is not an error, if a semaphore is 0 the function waits until it is positive).

Posting on a semaphore

A value of a semaphore is increased with the function: sem_post.