Synchronizing Threads with Semaphores

Use semaphores to mutually exclude competing threads or control access to a resource. A semaphore is a built-in 4Test data type that can only be assigned a value once. The value must be an integer greater than zero. Once it is set, your code can get the semaphore's value, but cannot set it.


The following code example shows legal and illegal manipulations of a variable of type SEMAPHORE:
SEMAPHORE semA = 10                        // Legal
semA = 20                                  // Illegal - existing semaphore
                                           // cannot be reinitialized
if (semA == [SEMAPHORE]2)...               // Legal - note the typecast
Print ("SemA has {semA} resources left.")  // Legal
SEMAPHORE semB = 0                         // Illegal - must be greater than 0

To compare an integer to a semaphore variable, you must typecast from integer to semaphore using [SEMAPHORE].

Note: You cannot cast a semaphore to an integer.

To use a semaphore, you first declare and initialize a variable of type SEMAPHORE. Thereafter, 4Test controls the value of the semaphore variable. You can acquire the semaphore if it has a value greater than zero. When you have completed your semaphore-protected work, you release the semaphore. The Acquire function decrements the value of the semaphore by one and the Release function increments it by one. Thus, if you initialize the semaphore to 5, five threads can simultaneously execute semaphore-protected operations while a sixth thread has to wait until one of the five invokes the Release function for that semaphore.

The Acquire function either blocks the calling thread because the specified semaphore is zero, or "acquires" the semaphore by decrementing its value by one. Release checks for any threads blocked by the specified semaphore and unblocks the first blocked thread in the list. If no thread is blocked, Release "releases" the semaphore by incrementing its value by one so that the next invocation of Acquire succeeds, which means it does not block.

A call to Acquire has the following form:
void Acquire(SEMAPHORE semA)
Where semA s the semaphore variable to acquire.
A call to Release has the following form:
void Release(SEMAPHORE semA)
Where semA s the semaphore variable to release.

If more than one thread was suspended by a call to Acquire, the threads are released in the order in which they were suspended.

A semaphore that is assigned an initial value of 1 is called a binary semaphore, because it can only take on the values 0 or 1. A semaphore that is assigned an initial value of greater than one is called a counting semaphore because it is used to count a number of protected resources.

Example: Application only supports three simultaneous users

Suppose you are running a distributed test on eight machines using eight 4Test threads. Assume that the application you are testing accesses a database, but can support only three simultaneous users. The following code uses a semaphore to handle this situation:
Acquire (DBUsers)
  access database
Release (DBUsers)
The declaration of the semaphore is global; each thread contains the code to acquire and release the semaphore. The initial value of three ensures that no more than three threads will ever be executing the database access code simultaneously.