A semaphore is a synchronization primitive that acts like a gate that lowers to prevent passage of a thread through code and raises to allow passage (of another thread) through that code. A semaphore is similar to a mutex, and can be used instead of a mutex.
The following code illustrates the use of semaphores:
Working-Storage Section. 01 data-semaphore usage semaphore-pointer. 01 data-value pic x(4) comp-x value 0. * Initialization code executed while in single threaded mode open data-semaphore set data-semaphore up by 1 *> Initialize as raised * Add change data-value, this is a critical section set data-semaphore down by 1 add 1 to data-value * Allow other thread to pass semaphore set data-semaphore up by 1
Note that just after the OPEN verb, the semaphore is raised by 1. This enables the first acquisition of the semaphore to succeed but any following acquisitions to be blocked until the semaphore is released again.
A semaphore is less efficient than a mutex, but it is more flexible: one thread can simply release a semaphore while any other thread can then acquire it. Contrast this with a mutex. A mutex must always be acquired before it can be released, and the operations of acquiring and releasing it must happen within the same thread. Semaphores provide a way of signaling from one thread to another.
The following code sample uses two different semaphores to establish handshaking between two threads. This handshaking enables one thread to signal the production of a new data value and the other thread to signal the corresponding consumption of that data value:
Working-Storage Section. 01 produced-semaphore usage semaphore-pointer. 01 data-value pic x(4) comp-x value 0. 01 consumed-semaphore usage semaphore-pointer. * Initialization code executed while in single threaded mode open produced-semaphore open consumed-semaphore set consumed-semaphore up by 1 * This code is executed once to produce a data value set consumed-semaphore down by 1 add 10 to data-value * Signal that data value has changed set produced-semaphore up by 1 * Another thread, waiting for the data-value to change, * executes this code once as well. set produced-semaphore down by 1 display data-value * Signal data value used set consumed-semaphore up by 1
This example illustrates another common synchronization problem known as the Producer-Consumer Problem. The simplest Producer-Consumer Problem is where there is one thread that produces data, and one thread that consumes that data, and you need to synchronize execution between the producing and consuming threads so that when the consumer is active it always has data to operate on.
The semaphores in this example count the number of releases that are outstanding on a semaphore and allow that many acquires to pass unblocked. This kind of counting semaphore enables the producer to produce multiple data values (usually in an array) before blocking to wait for the consumer to catch up.
The following code illustrates a simple example of a producer/consumer pair where the producer is allowed to create data values until the data table cannot handle any more - at which point the producer blocks the creation of values until some values are consumed:
Working-Storage Section. 78 table-size value 20. 01 produced-semaphore usage semaphore-pointer. 01 filler. 05 table-data-value pic x(4) comp-x occurs table-size times value 0. 01 consumed-semaphore usage semaphore-pointer. Local-Storage Section. 01 table-i pic x(4) comp-x. * Initialization code executed while in single threaded mode open produced-semaphore open consumed-semaphore * Start raised 20 times set consumed-semaphore up by table-size * Producer thread move 1 to table-i perform until 1 = 0 set consumed-semaphore down by 1 add table-i to table-data-value(table-i) set produced-semaphore up by 1 add 1 to table-i if table-i > table-size move 1 to table-i end-if end-perform. * Consumer thread move 1 to table-i perform until 1 = 0 set produced-semaphore down by 1 display 'Current produced value is' table-data-value(table-i) set consumed-semaphore up by 1 add 1 to table-i if table-i > table-size move 1 to table-i end-if end-perform.