Using a Semaphore

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.

Example

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.

Example

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.

Example

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.