Thread synchronization

If two or more of your threads need to access common data, thenyou need to use some form of synchronization to keep more than one thread accessingthe data at the same. For this topic we are discussing services available in thethreading encapsulation library.

For Modula-2 this module is Threads.

For Ada95 this package is Sbs.Threads.

The compiler also implements multi-processingsupport for inline code generation of atomic operations.

Classes of synchronization objects

CriticalSection

MutexSem

Exclusion objects provide a mechanism for allowing only a singlethread to "own" an exclusion object and therefore any code protected byan exclusion object can only be executed by a single thread. Once a thread owns anexclusion object it must release its ownership of the object. Only the thread thatowns the exclusion object may release it. There are many forms of exclusion objectssupported by the threading library and the merits of each are discussed in the topicCriticalSection vs Spinlock vs MutexSem. Spinlocksalso have a topic discussing how to implement them.

EventSem

Events provide a mechanism to signal an "event". Anevent has two states, set and clear. When an event is set any thread that waits foran event will be allowed to continue. If the event is clear then any thread thatwaits on the event will not execute until the event is set. When an event is setall threads waiting for the event are allowed to continue.

SignalSem

Signals maintain a count of the number of signals sent and onlyone thread is allowed to run for each sent signal. Therefore if three threads arewaiting for a signal and only one signal has been sent, a signal count of one, thenonly one thread will run. The signal count is automatically decremented by one foreach thread that is allowed to execute. Signals have a user defined maximum numberof sent signals. Because of this signals are a good choice for limiting access tofinite resources. Signals with a maximum count of one can be used as a kind of exclusionobject that is not specifically owned by a given thread.

ConditionVariable

Condition variables are a combination of a CriticalSection anda kind of event. When signaling the condition you can start one thread or all threadswaiting on the condition. This object performs an atomic uninterruptable operationof leaving a CriticalSection and waiting for the "condition". You mustown the CriticalSection on entry to the condition wait. When the condition wait returnsyou again own the CriticalSection.

RwLock

Reader/writer locks allow multiple read locks and only one writelock to exist at any point in time. If a read lock is in place then no write lockcan be obtained, however another thread can obtain another read lock. If a writelock is in place then exclusive access is granted, meaning no other write lock orread locks are granted while a write lock is in effect.

Barrier

A barrier is a point of synchronization in code for multiplethreads. A barrier causes all threads that wait at the barrier to suspend until aspecified number of threads are waiting at the barrier. Then all threads that havereached the barrier are allowed to continue. It can be determined which thread wasthe last to reach the barrier.

Multiple wait objects

You can wait on multiple objects (MutexSem, EventSem, SignalSem)with a single call. These objects are called MultiSem. You can wait for any singleobject to be available, or you can wait for all objects to be available simultaneously.When you wait for a single object in a MultiSem any object in the wait will satisfythe wait. You are notified which object in the wait succeeded. If you wait for allobjects to be available simultaneously then each MutexSem in the wait must not beowned, each EventSem must be set and each SignalSem must have a signal count greaterthan one, all at the same instant in time. For example if you were waiting for twoevents simultaneously, then both events must be set at the same time. One or bothevents might become set and reset individually during the lifetime of the wait butthe wait is not satisfied unless both are in the set state at the same time.

Compound synchronization objects

A compound synchronization object is one where two or more primitivesynchronization objects are combined to create a synchronization object with propertiesnot available with the primitive objects alone.

Be aware that testing, even extensive testing, is not good enoughfor thread synchronization when using compound synchronization objects. This is becauseboundary conditions may occur only once in thousands of test runs. You need to geta pen and paper and map out the transitions as multiple threads are executing andcontending for a compound synchronization object, with each thread at various possiblelines of code in the compound object. Assume that each thread contending will betask switched at all points in the compound object algorithm. It is very easy toget lock out situations where no threads can execute and/or situations where a threadslips though a compound object when it should not get through. Simple compound objectsare obviously much easier to implement.

Exceptions

Don't forget to handle exceptions in code that takes ownershipof synchronization objects. You might want to have exception handlers release ownershipof an object. The exception handler can continue the let the exception propagateafter releasing the object. Doing this can allow your program to continue executionor exit gracefully by avoiding potential lockout situations because of an exception.

Examples

For an excellent example of synchronizing multiple threads accessingcommon data refer to the runtime library module Pipes.

If your algorithm requires that you gain ownership of two separateexclusion objects then you should make sure you release the exclusion objects inthe reverse order in which you gained ownership, otherwise you are priming the systemfor a lockout situation where no threads run.

EnterCriticalSection(readLock);

EnterCriticalSection(mutex);

...

LeaveCriticalSection(mutex);

LeaveCriticalSection(readLock);

In some algorithms you may never be able to avoid a lockout withoutwaiting for both objects simultaneously using a MultiSem. In this case you are restrictedto using a MutexSem as the exclusion objects since its is the only one that can beused with MultiSem.

The following is a more common situation with events.

WaitForEventSem(send, SemWaitForever);

WaitForEventSem(queue, SemWaitForever);

This is probably better written by creating a MultiSem with thesend and queue event objects that waits for both events to be simultaneously set.