Threads and Semaphores: 5.2 Synchronizing Threads: Semaphores In GEOS

Up: GEOS SDK TechDocs | Up | Prev: 5.1 Semaphores: The Concept

GEOS contains several system resources that are protected by semaphores. Since application programs can access these resources only through library routines, the programmer does not need to be aware of these semaphores; the required operations are performed by the library routines. For system resources (e.g. files, memory, handles), GEOS defines semaphores and provides special routines to set and reset them. The chapter that describes each system resource explains how to use the special semaphores that protect the resource.

The routines described in this section illustrate GEOS semaphores and can be used to create semaphores to protect resources defined within a multithreaded application. There are routines for each of the operations (initialization, P, and V), and there are special routines which simplify the use of a semaphore by multiple objects within the same thread.

Note that it is possible to create a semaphore with a starting value greater than one. That is, you can create a semaphore that will allow more than one thread to grab it at once. Typically, if only one thread may grab the semaphore, the thread is called a "mutual exclusion," or "mutex," semaphore, because it is normally used to make sure two threads don't mutually grab a particular resource.

Semaphores that can be grabbed by more than one thread are generally called "scheduling semaphores" because they allow easy manipulation of scheduled resources. The classic example of this is the "producer-consumer problem" wherein one thread produces buffers and another consumes them. Initially, no buffers exist, so the semaphore starts at zero. The consumer goes into a loop wherein it simply P's the semaphore (blocking until a buffer exists), takes the first buffer in the queue, processes the buffer, destroys the buffer, and then returns to the top of the loop. The producer, meanwhile, can produce any number of buffers, queue them, and V the semaphore once for each consumable buffer. The consumer thread will continue to process until all the buffers are consumed, and then it will block and wait for more buffers.

Operations on a Semaphore

ThreadAllocSem(), ThreadPSem(), ThreadPTimedSem(), ThreadVSem(), ThreadFreeSem()

To create a semaphore, simply call the routine ThreadAllocSem() , passing an initial value for the semaphore. This should normally be one to indicate the semaphore is unlocked. If you want the semaphore to be locked initially, pass an initial value of zero. In either case, the returned value will be the handle of the newly created semaphore. Use this handle with the routines described below.

Once a semaphore is created, a thread can lock it (i.e., perform the "P" operation) by calling the routine ThreadPSem() , passing the semaphore's handle as an argument. If the semaphore is unlocked, the thread locks it and proceeds; otherwise the thread waits in the semaphore's thread queue.

Another routine that performs the "P" operation is ThreadPTimedSem() . When calling this routine, pass as arguments the semaphore's handle and an integer representing a number of ticks. This integer is a time limit: If another thread has the semaphore locked and does not unlock it within the specified number of ticks, the routine will return with a flag indicating the lock was unsuccessful. Programs that use ThreadPTimedSem() must check this flag and must not perform the protected operation if it is set. The most common use of this routine is with a time limit of zero, meaning that the semaphore should be locked only if it is available right away--if it is not available, the thread will continue with some other action.

To release the semaphore (by performing the "V" operation), the thread calls ThreadVSem() , again passing the semaphore's handle. If there are other threads waiting for the semaphore, the one with the lowest current priority number takes over.

When a semaphore is no longer needed, it can be destroyed by calling ThreadFreeSem() with the semaphore's handle as an argument.

Operations on a Thread Lock

ThreadAllocThreadLock(), ThreadGrabThreadLock(), ThreadReleaseThreadLock(), ThreadFreeThreadLock()

At times it is convenient to have a program lock a semaphore that it has already locked. For example, one routine might lock a semaphore protecting a piece of memory and then call itself recursively. A thread lock is a semaphore that can be locked any number of times, as long as each lock is performed by the same thread. If another thread tries to grab the thread lock, it will wait until the first thread has performed the "V" operation once for each time it has run the "P" operation. It is possible to write reentrant routines using thread locks but not using regular semaphores.

A thread lock is initialized with the ThreadAllocThreadLock() routine, which takes no arguments. A thread lock is always created unlocked. To perform the "P" and "V" operation on a thread lock, use ThreadGrabThreadLock() and ThreadReleaseThreadLock() , respectively, and pass the semaphore's handle as an argument. These routines are analogous to ThreadPSem() and ThreadVSem() for semaphores. When a thread lock is no longer needed, it should be freed with a call to ThreadFreeThreadLock().


Up: GEOS SDK TechDocs | Up | Prev: 5.1 Semaphores: The Concept