PTypes: multithreading: semaphore

Top: Multithreading: semaphore & timedsem

>     #include <pasync.h>

>     class semaphore {
>         semaphore(int initvalue);
>         void wait();
>         void post();
>         void signal();    // alias for post()
>     }

>     class timedsem {
>         timedsem(int initvalue);
>         bool wait( [ int milliseconds ] );
>         void post();
>         void signal();
>     }

Semaphore is a special helper object with very simple logic which is typically used to synchronize the execution of concurrent threads. A semaphore object can be considered as an integer value which has one additional feature: if its value is 0, an attempt to decrement this value will cause the calling thread to "hang" until some other thread increments it. "Hanging" on the semaphore means entering effective wait state and consuming no or little CPU time, depending on the operating system.

One example showing the use of semaphores is when one thread needs to send data (e.g. through a buffer) to another thread. In multithreading environments there is no guarantee in which order two threads will come to the point where the first thread is filling the data buffer and the other thread is reading it. Therefore, these two threads need to synchronize execution at the exchange point. Semaphore's logic for this case is fairly simple: the reader thread calls wait() before reading the buffer and "hangs" if the semaphore is not yet signaled. The writer thread calls post() after filling the buffer with data and thus signals the reader thread that the data buffer is ready. This schema ensures that the data buffer will be read by the second thread only when the data is actually ready.

If the data exchange cycle is iterative you will have to make sure also that the buffer is not filled twice before the reading thread takes the first data chunk. In this situation another semaphore should be created with reverse logic: the semaphore is set to signaled state when the reading thread has taken the first data chunk and is ready to take the second chunk. The writing thread, in its turn, waits on this semaphore to make sure the buffer is ready for the successive data chunk.

In more complex applications when many threads need to exchange data with each other or with the main application thread, message queues can be used instead of semaphores. The message queue object itself is another example of using semaphores (please see pmsgq.cxx source file).

You can use semaphores when your application needs to limit the number of concurrently running threads of the same type. A typical web robot application, for example, creates a new thread for each download process. To limit the number of threads the application creates a semaphore with the initial value equal to the maximum allowed number of threads. Each new thread decrements the semaphore by calling wait() and then increments it with post() upon termination. If the maximum allowed number of threads is reached, the next thread calling wait() will "hang" until one of the running threads terminates and calls post().

PTypes' semaphore object encapsulates either Windows semaphore or an implementation based on POSIX synchronization primitives. This object implements the minimal set of features common to both Windows and POSIX semaphores. Besides, semaphores can not be shared between processes on some operating systems, thus limiting the use of PTypes' semaphores to one process.

PTypes' timedsem adds timed waiting feature to the simple semaphore. This class has an interface compatible with the simple semaphore with one additional function - wait(int) with timer. The reason this feature is implemented in a separate class is that not all platforms support timed waiting. Wherever possible, PTypes uses the system's native sync objects, or otherwise uses its own implementation based on other primitives. Note that timedsem can be used both for infinitely waiting and timed waiting; it is, however, recommended to use simple semaphore if you are not going to use timed waiting.

As an example of using timedsem see implementation of thread 'relaxing' mechanism in include/pasync.h and src/pthread.cxx.

semaphore::semaphore(int initvalue) constructs a semaphore object with the initial value initvalue.

void semaphore::wait() decrements the semaphore's value by 1. wait() can enter effective wait state if the value becomes -1, in which case the thread will "hang" until some other thread increments the value by calling post().

void semaphore::post() increments the semaphore's value by 1. post() can release some other thread waiting for the same semaphore if its value was -1.

void semaphore::signal() is an alias for post().

timedsem::timedsem(int initvalue) constructs a semaphore object with an interface fully compatible with (but not inherited from) semaphore. This class has one additional method for timed waiting (see below).

bool timedsem::wait( [ int milliseconds ] ) decrements the semaphore's value by 1 and enters effective wait state if the value becomes -1. Unlike simple wait() this function will 'wake' and return if the time specified in milliseconds has elapsed, in which case the function returns false. If the semaphore was signaled with post() or signal() before the time elapses this function returns true. If milliseconds is omitted or is -1 the function will wait infinitely, like simple wait().

See also: thread, mutex, rwlock, trigger, msgqueue, Examples

PTypes home