Storage Synchronization Concepts

This section describes some background information and considerations related to shared storage synchronization.

Shared Storage

The instructions described in this chapter pertain to shared storage, which, in this context, refers to any space data which is accessed from two or more threads of execution.

Common implementations of shared storage consist of multiple threads using direct or indirect (pointer-based) access to read from and write to locations in the following types of spaces:

Shared Storage Access Ordering

When threads share storage, there is no guarantee that shared storage accesses (reads/writes) performed by one thread will be observed to occur in a particular order by other threads, unless some form of explicit storage synchronization is performed by both the threads writing the shared storage and the threads reading the shared storage.

Storage synchronization is only required when two or more threads are attempting concurrent access to shared storage, and the semantics of the threads' logic dictate that some order be enforced on the shared storage accesses. When the order in which shared storage updates are observed is not important, no storage synchronization is necessary. A given thread will always observe its own storage updates (to shared or non-shared storage) in order. In addition, accesses to overlapping shared storage locations will always be observed to occur in the same order by all threads.

A typical scenario which requires some form of explicit data synchronization is when the state of one shared storage location is used (by convention in a program's logic) to control access to a second (non-overlapping) shared storage location. For example, consider such a situation, where one thread initializes some shared data (DATA) and then sets a shared flag (FLAG) to indicate to all other threads that the shared data has been initialized.


    Initializing Thread                     All Other Threads
    -------------------                 ---------------------------
         DATA = 10                      loop until FLAG has value 1
         FLAG = 1                       use DATA

In this case, we must enforce an order on the shared storage accesses. Otherwise, the initializing thread's shared storage updates could be observed out of order by other threads, which could allow some or all of the other threads to read an uninitialized value from DATA. The remainder of this discussion and the ensuing instructions describe how to enforce shared storage access ordering.

Storage Synchronizing Actions

When an ordering of shared storage accesses is required, all threads for which an order must be enforced must take explicit action to synchronize the shared storage accesses. These actions are called storage synchronizing actions.

When a thread performs a storage synchronizing action, shared storage accesses will be ordered in the sense that accesses appearing in the logical flow of the thread's code before the synchronizing action will be guaranteed to be completed, from the standpoint of the issuing thread, before those appearing in the logical flow after the synchronizing action. If two writes to two shared locations are separated by a synchronizing action, the first write is guaranteed to be available to other threads at or before their next synchronizing actions, and no later than the second write becomes available. If two reads from two shared locations are separated by a storage synchronizing action, then the second read will read a value no less current than the first read, providing an ordering was enforced by other threads when writing to the shared storage.

As stated above, to completely enforce shared storage access ordering between two or more threads, it is necessary that all threads dependent on the access ordering -- both readers and writers of the shared data -- use appropriate synchronizing actions. It is not sufficient to only perform storage synchronization in threads writing to shared storage.

To continue with the example used above, to enforce access ordering on the shared data and flag values, we could use the SYNCSTG operation (a storage synchronizing operation defined later in this chapter) as follows:


    Initializing Thread                     All Other Threads
    -------------------                 ---------------------------
          DATA = 10                     loop until FLAG has value 1
          SYNCSTG(0)                    SYNCSTG(0)
          FLAG = 1                      use DATA

In the initializing thread, the storage synchronizing action ensures that the value of FLAG is available to other threads no earlier than the value of DATA. In the other threads, the storage synchronizing action ensures that the value of DATA is at least as current as the value of FLAG. Without the synchronizing action between the two reads, the reading threads could potentially observe the updated value of FLAG but still not see the new value of DATA. Note that since the initializing thread is using SYNCSTG to enforce an ordering on shared storage writes, the value two (2) could have been specified for the action operand. Likewise, the other threads, which are simply trying to enforce an ordering on reads, could have used an action value of one (1).

In addition to the instructions described in this chapter, some other MI instructions also have storage synchronizing side effects under certain circumstances. The synchronizing actions associated with the following operations have an ordering effect on shared storage accesses equivalent to that achieved by the SYNCSTG operation with an action code value of zero (which indicates that both shared storage reads and writes are affected). The list below spans the instructions used for synchronizing thread execution. When the execution of threads which are writing to shared storage are serialized with other threads accessing the storage using these synchronization mechanisms, shared storage accesses will be synchronized as well. The SYNCSTG operation is useful when, for performance or other reasons, accesses are not completely serialized using these other mechanisms, as in the example above.


MI Instruction Synchronizing Action
Lock Object (LOCK) Successful acquisition of the lock.
Unlock Object (UNLOCK) Successful release of the lock.
Lock Space Location (LOCKSL) Successful acquisition of the lock.
Unlock Space Location (UNLOCKSL) Successful release of the lock.
Lock Pointer-Based Mutex (LOCKMTX) Successful acquisition of the mutex.
Unlock Pointer-Based Mutex (UNLKMTX) Successful release of the mutex.
Enqueue (ENQ) Successful completion of the enqueue.
Dequeue (DEQ) Successful completion of the dequeue.
Compare And Swap (CMPSW) Successful update of the target (2nd operand) value. (synchronizes storage only when synchronization is not disabled using the optional control flag available for this instruction from bound programs)
Check Lock Value (CHKLKVAL) Successful update of the target (1st operand) value.
Clear Lock Value (CLRLKVAL) Update of the target value.

There are other higher-level synchronization mechanisms that are built on top of these MI instructions which also have storage synchronizing effects. See the storage synchronization chapter of the "ILE Concepts" manual (SC41-3606) for more details.