Thread synchronization is essential when multiple threads access shared resources concurrently in COBOL applications. Without proper synchronization, programs can experience race conditions, data corruption, and inconsistent states. This guide covers synchronization mechanisms including mutex locks, read/write locks, semaphores, condition variables, and best practices for safe concurrent programming.
When multiple threads execute concurrently and access shared data, synchronization mechanisms coordinate their access to prevent conflicts. The goal is to ensure that:
| Mechanism | Purpose | Best Use Case |
|---|---|---|
| Mutex Lock | Allows only one thread to access a resource | Protecting critical sections with exclusive access |
| Read/Write Lock | Allows multiple readers or one writer | Many readers, few writers (e.g., configuration data) |
| Semaphore | Limits number of concurrent threads | Controlling access to resource pools |
| Condition Variable | Blocks threads until condition is met | Producer-consumer patterns, waiting for events |
A mutex (mutual exclusion) lock ensures that only one thread can execute a critical section at a time. When a thread acquires a mutex, other threads must wait until it's released.
123456789101112131415161718192021222324252627282930313233343536373839404142434445WORKING-STORAGE SECTION. 01 LOCK-CONTROL. 05 LOCK-STATUS PIC X VALUE 'N'. 88 LOCK-ACQUIRED VALUE 'Y'. 88 LOCK-FREE VALUE 'N'. 05 LOCK-OWNER PIC X(8). 05 LOCK-TIMEOUT PIC 9(3) VALUE 30. 01 SHARED-RESOURCE. 05 SHARED-COUNTER PIC 9(8) VALUE 0. 05 SHARED-DATA PIC X(100). PROCEDURE DIVISION. *> Acquire lock before accessing shared resource PERFORM ACQUIRE-LOCK IF LOCK-ACQUIRED *> Critical section - modify shared data ADD 1 TO SHARED-COUNTER MOVE 'Updated data' TO SHARED-DATA *> Release lock after critical section PERFORM RELEASE-LOCK ELSE DISPLAY 'Failed to acquire lock' END-IF. ACQUIRE-LOCK. *> Attempt to acquire lock IF LOCK-FREE MOVE 'Y' TO LOCK-STATUS MOVE 'THREAD-01' TO LOCK-OWNER DISPLAY 'Lock acquired by ' LOCK-OWNER ELSE DISPLAY 'Lock is busy, waiting...' *> In real implementation, would wait or timeout PERFORM WAIT-FOR-LOCK END-IF. RELEASE-LOCK. IF LOCK-ACQUIRED MOVE 'N' TO LOCK-STATUS MOVE SPACES TO LOCK-OWNER DISPLAY 'Lock released' END-IF.
12345678910111213141516171819202122232425262728293031PROCEDURE DIVISION. *> Protect file update operation PERFORM ENTER-CRITICAL-SECTION *> Critical section: update shared file READ SHARED-FILE INVALID KEY DISPLAY 'Record not found' NOT INVALID KEY COMPUTE NEW-VALUE = CURRENT-VALUE + INCREMENT MOVE NEW-VALUE TO CURRENT-VALUE REWRITE SHARED-RECORD INVALID KEY DISPLAY 'Update failed' NOT INVALID KEY DISPLAY 'Update successful' END-REWRITE END-READ *> Always exit critical section PERFORM EXIT-CRITICAL-SECTION. ENTER-CRITICAL-SECTION. PERFORM ACQUIRE-LOCK IF NOT LOCK-ACQUIRED DISPLAY 'Cannot enter critical section' PERFORM ERROR-HANDLING END-IF. EXIT-CRITICAL-SECTION. PERFORM RELEASE-LOCK.
Read/write locks allow multiple threads to read simultaneously but only one thread to write at a time. This provides better concurrency than mutex locks when you have many readers and few writers.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758WORKING-STORAGE SECTION. 01 READ-WRITE-LOCK. 05 READER-COUNT PIC 9(4) VALUE 0. 05 WRITER-ACTIVE PIC X VALUE 'N'. 88 WRITER-PRESENT VALUE 'Y'. 88 NO-WRITER VALUE 'N'. 05 LOCK-STATUS PIC X. 88 LOCK-ACQUIRED VALUE 'Y'. PROCEDURE DIVISION. *> Reader thread: acquire read lock PERFORM ACQUIRE-READ-LOCK IF LOCK-ACQUIRED *> Multiple readers can access simultaneously READ CONFIGURATION-FILE AT END DISPLAY 'End of file' NOT AT END DISPLAY 'Reading: ' CONFIG-DATA END-READ PERFORM RELEASE-READ-LOCK END-IF. ACQUIRE-READ-LOCK. *> Wait if writer is active PERFORM UNTIL NO-WRITER IF WRITER-PRESENT DISPLAY 'Waiting for writer to finish...' PERFORM WAIT-SHORT-TIME END-IF END-PERFORM *> Increment reader count ADD 1 TO READER-COUNT MOVE 'Y' TO LOCK-STATUS. RELEASE-READ-LOCK. SUBTRACT 1 FROM READER-COUNT IF READER-COUNT = 0 MOVE 'N' TO LOCK-STATUS END-IF. ACQUIRE-WRITE-LOCK. *> Wait until no readers and no other writer PERFORM UNTIL READER-COUNT = 0 AND NO-WRITER DISPLAY 'Waiting for readers/writers to finish...' PERFORM WAIT-SHORT-TIME END-PERFORM *> Acquire exclusive write lock MOVE 'Y' TO WRITER-ACTIVE MOVE 'Y' TO LOCK-STATUS. RELEASE-WRITE-LOCK. MOVE 'N' TO WRITER-ACTIVE MOVE 'N' TO LOCK-STATUS.
Semaphores control access to a resource by limiting the number of threads that can access it concurrently. Unlike mutex locks (which allow only one thread), semaphores can allow multiple threads up to a specified count.
1234567891011121314151617181920212223242526272829303132333435363738394041WORKING-STORAGE SECTION. 01 SEMAPHORE-CONTROL. 05 SEMAPHORE-COUNT PIC 9(4) VALUE 5. 05 MAX-COUNT PIC 9(4) VALUE 5. 05 WAITING-COUNT PIC 9(4) VALUE 0. PROCEDURE DIVISION. *> Acquire semaphore (wait if count is 0) PERFORM SEMAPHORE-WAIT *> Access limited resource (e.g., connection pool) PERFORM USE-RESOURCE *> Release semaphore PERFORM SEMAPHORE-SIGNAL. SEMAPHORE-WAIT. *> Decrement count, wait if count becomes negative IF SEMAPHORE-COUNT > 0 SUBTRACT 1 FROM SEMAPHORE-COUNT DISPLAY 'Semaphore acquired, count: ' SEMAPHORE-COUNT ELSE *> Resource unavailable, must wait ADD 1 TO WAITING-COUNT DISPLAY 'Waiting for semaphore, ' WAITING-COUNT ' threads waiting' PERFORM WAIT-FOR-SEMAPHORE SUBTRACT 1 FROM WAITING-COUNT SUBTRACT 1 FROM SEMAPHORE-COUNT END-IF. SEMAPHORE-SIGNAL. *> Increment count, wake waiting threads if any ADD 1 TO SEMAPHORE-COUNT IF SEMAPHORE-COUNT > MAX-COUNT MOVE MAX-COUNT TO SEMAPHORE-COUNT END-IF IF WAITING-COUNT > 0 DISPLAY 'Signaling waiting thread, count: ' SEMAPHORE-COUNT PERFORM WAKE-WAITING-THREAD END-IF.
Deadlocks occur when threads are blocked forever, each waiting for a resource held by another. Prevent deadlocks by establishing consistent lock ordering.
12345678910111213141516171819202122232425262728293031323334353637383940414243PROCEDURE DIVISION. *> Always acquire locks in consistent order *> Use resource IDs to determine order IF RESOURCE-A-ID < RESOURCE-B-ID PERFORM LOCK-RESOURCE-A PERFORM LOCK-RESOURCE-B ELSE PERFORM LOCK-RESOURCE-B PERFORM LOCK-RESOURCE-A END-IF *> Perform operations on both resources PERFORM OPERATIONS-WITH-BOTH-RESOURCES *> Release locks in reverse order IF RESOURCE-A-ID < RESOURCE-B-ID PERFORM UNLOCK-RESOURCE-B PERFORM UNLOCK-RESOURCE-A ELSE PERFORM UNLOCK-RESOURCE-A PERFORM UNLOCK-RESOURCE-B END-IF. LOCK-RESOURCE-A. PERFORM ACQUIRE-LOCK-FOR-A IF NOT LOCK-ACQUIRED DISPLAY 'Failed to lock resource A' PERFORM HANDLE-LOCK-FAILURE END-IF. LOCK-RESOURCE-B. *> Only attempt if resource A is already locked IF RESOURCE-A-LOCKED PERFORM ACQUIRE-LOCK-FOR-B IF NOT LOCK-ACQUIRED *> Release A before failing PERFORM UNLOCK-RESOURCE-A DISPLAY 'Failed to lock resource B' PERFORM HANDLE-LOCK-FAILURE END-IF ELSE DISPLAY 'Error: Resource A not locked first' END-IF.
123456789101112131415161718192021222324252627282930313233343536WORKING-STORAGE SECTION. 01 LOCK-TIMEOUT-CONTROL. 05 LOCK-TIMEOUT PIC 9(3) VALUE 30. 05 LOCK-START-TIME PIC 9(8). 05 CURRENT-TIME PIC 9(8). 05 ELAPSED-TIME PIC 9(8). PROCEDURE DIVISION. *> Record lock acquisition time ACCEPT LOCK-START-TIME FROM TIME PERFORM ACQUIRE-LOCK-WITH-TIMEOUT IF LOCK-ACQUIRED *> Check if we're still within timeout PERFORM CHECK-TIMEOUT IF TIMEOUT-EXCEEDED PERFORM RELEASE-LOCK DISPLAY 'Lock timeout exceeded, releasing' PERFORM HANDLE-TIMEOUT ELSE *> Proceed with critical section PERFORM CRITICAL-SECTION-OPERATIONS PERFORM RELEASE-LOCK END-IF END-IF. CHECK-TIMEOUT. ACCEPT CURRENT-TIME FROM TIME COMPUTE ELAPSED-TIME = CURRENT-TIME - LOCK-START-TIME IF ELAPSED-TIME > LOCK-TIMEOUT SET TIMEOUT-EXCEEDED TO TRUE ELSE SET TIMEOUT-OK TO TRUE END-IF.
Race conditions occur when the outcome depends on the timing of thread execution. Prevent them by using atomic operations and proper locking.
12345678910111213141516171819202122*> PROBLEMATIC: Check-then-act pattern (race condition) IF COUNTER < MAX-LIMIT ADD 1 TO COUNTER *> Another thread might modify COUNTER here END-IF. *> BETTER: Atomic operation with lock PERFORM ACQUIRE-LOCK IF LOCK-ACQUIRED IF COUNTER < MAX-LIMIT ADD 1 TO COUNTER MOVE 'Y' TO INCREMENT-SUCCESSFUL ELSE MOVE 'N' TO INCREMENT-SUCCESSFUL END-IF PERFORM RELEASE-LOCK END-IF IF INCREMENT-SUCCESSFUL = 'Y' PERFORM CONTINUE-PROCESSING ELSE PERFORM HANDLE-LIMIT-REACHED END-IF.
1234567891011121314151617*> Use atomic operations when possible *> ADD and MOVE are typically atomic ADD 1 TO RECORD-COUNT *> Atomic increment *> Avoid complex computations that might be interrupted *> BAD: COMPUTE RECORD-COUNT = RECORD-COUNT + 1 *> GOOD: ADD 1 TO RECORD-COUNT *> Atomic flag setting MOVE 'Y' TO PROCESSING-FLAG *> Atomic *> Thread-safe counter with lock PERFORM ACQUIRE-LOCK IF LOCK-ACQUIRED ADD INCREMENT-VALUE TO SHARED-COUNTER PERFORM RELEASE-LOCK END-IF.
Reduce the amount of shared data between threads to minimize synchronization overhead and potential contention. Use local variables when possible.
Keep critical sections as short as possible to minimize the time locks are held, reducing contention and improving performance.
Always release locks in the same order they were acquired, and ensure locks are released even when errors occur (use exception handling).
Choose the right synchronization mechanism: mutex for exclusive access, read/write locks for many readers, semaphores for resource pools.
Always acquire multiple locks in the same order across all threads to prevent deadlocks. Use resource IDs or other consistent ordering criteria.
Implement timeouts for lock acquisition to detect and recover from deadlock situations, preventing indefinite blocking.
123456789101112131415161718192021222324252627282930313233343536373839404142WORKING-STORAGE SECTION. 01 QUEUE-CONTROL. 05 QUEUE-LOCK PIC X VALUE 'N'. 05 QUEUE-COUNT PIC 9(4) VALUE 0. 05 MAX-QUEUE-SIZE PIC 9(4) VALUE 100. PROCEDURE DIVISION. *> Producer: add items to queue PERFORM PRODUCER-LOOP UNTIL END-OF-DATA *> Consumer: remove items from queue PERFORM CONSUMER-LOOP UNTIL QUEUE-EMPTY. PRODUCER-LOOP. *> Wait if queue is full PERFORM UNTIL QUEUE-COUNT < MAX-QUEUE-SIZE PERFORM WAIT-SHORT-TIME END-PERFORM *> Acquire lock and enqueue PERFORM ACQUIRE-LOCK IF LOCK-ACQUIRED PERFORM ENQUEUE-ITEM ADD 1 TO QUEUE-COUNT PERFORM RELEASE-LOCK PERFORM SIGNAL-CONSUMER END-IF. CONSUMER-LOOP. *> Wait if queue is empty PERFORM UNTIL QUEUE-COUNT > 0 PERFORM WAIT-FOR-PRODUCER END-PERFORM *> Acquire lock and dequeue PERFORM ACQUIRE-LOCK IF LOCK-ACQUIRED PERFORM DEQUEUE-ITEM SUBTRACT 1 FROM QUEUE-COUNT PERFORM RELEASE-LOCK PERFORM PROCESS-ITEM END-IF.
Imagine you and your friends want to use the same toy:
If everyone tries to grab the toy at the same time, it might break or someone might get hurt. So you make a rule: only one person can play with the toy at a time. When someone is done, they tell the next person it's their turn.
Thread synchronization is like making rules for sharing. A lock is like saying "I'm using this now" - when you have the lock, you can use the shared thing safely. When you're done, you give the lock to the next person.
Just like you take turns with toys, computer programs use locks to take turns with shared data, so everything stays safe and nothing gets broken!
Implement a thread-safe counter using a mutex lock. Multiple threads should be able to increment the counter safely.
123456*> Thread-safe counter increment PERFORM ACQUIRE-LOCK IF LOCK-ACQUIRED ADD 1 TO SHARED-COUNTER PERFORM RELEASE-LOCK END-IF.
Implement a read/write lock that allows multiple readers but only one writer at a time.
12345678910111213*> Reader: wait for no writer, increment reader count PERFORM UNTIL NO-WRITER IF WRITER-PRESENT PERFORM WAIT-SHORT-TIME END-IF END-PERFORM ADD 1 TO READER-COUNT *> Writer: wait for no readers and no other writer PERFORM UNTIL READER-COUNT = 0 AND NO-WRITER PERFORM WAIT-SHORT-TIME END-PERFORM MOVE 'Y' TO WRITER-ACTIVE.
1. What is the primary purpose of thread synchronization?
2. What is a mutex lock?
3. What is the main difference between mutex locks and read/write locks?
4. What is a deadlock?
5. How can you prevent deadlocks?