There are 4 solution to support multi-thread program; it's depend on customer's requirement which one can be used properly.(Details from MF document)
To launch a multi-thread program, please use 'cobrun_t' instead of 'cobrun'.
Three Kinds of Storage
- Working-Storage SectionData item in Working-Storage Section are global shared, among all threads.
- Local-Storage SectionLocal-Storage Section works as function local variable; for every recursion of the program a new instance of this data is allocated on the stack.
- Thread-Local-Storage SectionThread-Local-Storage Section, Thread-local data can be viewed as a thread-specific Working-Storage data item.
Solution 1: Using Compiler Directive SERIAL
Compile your COBOL programs with the SERIAL directive.
SERIAL causes the COBOL run-time system to serialize access to your COBOL code between different threads. Only one thread can access your program at a time. This is the safest option, although it has potentially the highest overhead for execution speed. It is suitable when the COBOL program is providing access to a shared resource (for example, a printer), or when the COBOL program called from Java is in turn calling other COBOL programs which have not been enabled for multi-threading.
For example:
MAINPROG will create 2 threads SUBPROG, and each thread will call a SUBSLEEP. Since SUBSLEEP will be called at the same time by two threads SUBPROG.
- If SERIAL compiler directive is set when compile SUBSLEEP program
$ make cob -ug MAINPROG.cbl -C "list(MAINPROG.lst)" -C XREF -C SETTINGS cob -ug SUBPROG.cbl -C "list(SUBPROG.lst)" -C XREF -C SETTINGS cob -ug SUBSLEEP.cbl -C "list(SUBSLEEP.lst)" -C XREF -C SETTINGS -C SERIAL $ cobrun_t MAINPROG SUBSLEEP ENTRY, WSS-VAR=00 SUBSLEEP EXIT, WSS-VAR=01 SUBSLEEP ENTRY, WSS-VAR=01 SUBSLEEP EXIT, WSS-VAR=02
We can find that only one SUBSLEEP can be run at a time. The second will not come in until the first has quit.
- If NO SERIAL compiler directive is set when compile SUBSLEEP program
$ make cob -ug MAINPROG.cbl -C "list(MAINPROG.lst)" -C XREF -C SETTINGS cob -ug SUBPROG.cbl -C "list(SUBPROG.lst)" -C XREF -C SETTINGS cob -ug SUBSLEEP.cbl -C "list(SUBSLEEP.lst)" -C XREF -C SETTINGS $ cobrun_t MAINPROG SUBSLEEP ENTRY, WSS-VAR=00 SUBSLEEP ENTRY, WSS-VAR=01 SUBSLEEP EXIT, WSS-VAR=02 SUBSLEEP EXIT, WSS-VAR=02
We can find both SUBSLEEP can be entry at the same time.
Notice: The WSS is global, the setting on first coming will affect the later value.
Solution 2: Making per-thread Working-Storage Section
Compile your COBOL programs with the REENTRANT"2" directive.
REENTRANT"2" causes the COBOL run-time system to allocate separate user data and FD file areas to each different thread. This prevents any conflicts or data corruption within the program. But REENTRANT"2" can't guarantee thread-safety if it calls other non-threaded programs, or accesses other shared resources - in these sorts of cases, SERIAL is a safer option. The REENTRANT"2" directive can provide better performance than SERIAL as one thread is not kept waiting for the next thread to finish.
(REENTRANT"1" is default setting)
In other words, REENTRANT"2" make WSS per-thread, so there is no shared data between different threads.
$ make
cob -ug MAINPROG.cbl -C "list(MAINPROG.lst)" -C XREF -C SETTINGS
cob -ug SUBPROG.cbl -C "list(SUBPROG.lst)" -C XREF -C SETTINGS
cob -ug SUBSLEEP.cbl -C "list(SUBSLEEP.lst)" -C XREF -C SETTINGS -C REENTRANT=2
$ cobrun_t MAINPROG
SUBSLEEP ENTRY, WSS-VAR=00
SUBSLEEP ENTRY, WSS-VAR=00
SUBSLEEP EXIT, WSS-VAR=01
SUBSLEEP EXIT, WSS-VAR=02
Solution 3: Using Thread-Local-Storage Section
Use thread-local storage in your COBOL programs instead of working storage.
Thread-local storage is allocated per thread, so there is no possibility of one thread corrupting the data used by another thread. This is fairly efficient, but might not always be an option with legacy code.
In fact, Solution 2 is trying to make Working-Storage Section work as Thread-Local-Storage Section.
Solution 4: Using Synchronized Operation
Multi-thread your COBOL program using COBOL multi-threading syntax or run-time system calls.
This can be very efficient as you control which data is thread-local and which data is shared between threads. You could use this option in COBOL driver programs that sit between the Java run-time environment and your legacy COBOL programs. Your driver program would be responsible for controlling access to the legacy programs, and would need to use semaphores or some similar mechanism to prevent two threads from accessing the same code at the same time.
You can use MUTEX to access variable exclusively.
With this solution, Working-Storage Section are not allocated pre-thread, they are shared, so it's depend on synchronized strategy to make variable access exclusively when necessary.
Appendix: Program Sources
MAINPROG.cbl
PROGRAM-ID. "MAINPROG". ENVIRONMENT DIVISION. WORKING-STORAGE SECTION. 78 THREAD-COUNT VALUE 2. 01 I1 PIC 9(2) COMP-5. 01 THREAD-ENTRY USAGE PROCEDURE-POINTER. 01 THREAD-RETURN USAGE POINTER. 01 SHARED-STORAGE OCCURS THREAD-COUNT TIMES. 03 THREAD-ID PIC 9(2) COMP-5. 03 THREAD-DATA PIC X(10). 03 THREAD-HANDLE USAGE POINTER. PROCEDURE DIVISION. * * DISPLAY "MAINPROG: ENTRY". SET THREAD-ENTRY TO ENTRY "SUBPROG" PERFORM VARYING I1 FROM 1 BY 1 UNTIL I1 > THREAD-COUNT MOVE I1 TO THREAD-ID(I1) MOVE I1 TO THREAD-DATA(I1) CALL "CBL_THREAD_CREATE_P" USING BY VALUE THREAD-ENTRY BY REFERENCE SHARED-STORAGE(I1) BY VALUE 0 * * BY VALUE LENGTH OF SHARED-STORAGE(I1) BY VALUE 1 BY VALUE 0 BY VALUE 0 BY REFERENCE THREAD-HANDLE(I1) IF RETURN-CODE NOT = 0 DISPLAY "FAIL: CANNOT CREATE THREAD" STOP RUN END-IF END-PERFORM PERFORM VARYING I1 FROM 1 BY 1 UNTIL I1 > THREAD-COUNT CALL 'CBL_THREAD_WAIT' USING BY VALUE THREAD-HANDLE(I1) BY REFERENCE THREAD-RETURN END-PERFORM. * DISPLAY "MAINPROG EXIT". STOP RUN. END PROGRAM "MAINPROG".
SUBPROG.cbl
PROGRAM-ID. "SUBPROG". WORKING-STORAGE SECTION. 01 SLEEP-PARM. 03 CALLER PIC X(10). 03 SECOND PIC 9(2) COMP-5. 03 TID PIC 9(2) COMP-5. LOCAL-STORAGE SECTION. LINKAGE SECTION. 01 THREAD-PARM. 03 THREAD-ID PIC 9(2) COMP-5. 03 THREAD-DATA PIC X(10). 03 THREAD-HANDLE USAGE POINTER. PROCEDURE DIVISION USING THREAD-PARM. * DISPLAY "SUBPROG ENTRY". MOVE "SUBPROG" TO CALLER. MOVE 2 TO SECOND. MOVE THREAD-ID TO TID. CALL "SUBSLEEP" USING SLEEP-PARM. * DISPLAY "SUBPROG EXIT". EXIT PROGRAM. END PROGRAM "SUBPROG"
SUBSLEEP.cbl
PROGRAM-ID. "SUBSLEEP". WORKING-STORAGE SECTION. 01 WSS-VAR PIC 9(02) VALUES 0. LOCAL-STORAGE SECTION. 01 SLEEPTIME PIC 9(8). 01 TIME-TODAY-1 PIC 9(8) VALUE 0. 01 TIME-TODAY-2 PIC 9(8) VALUE 0. 01 DELTATIME PIC 9(8) VALUE 0. LINKAGE SECTION. 01 SLEEP-PARM. 03 CALLER PIC X(10). 03 SECOND PIC 9(02) COMP-5. 03 TID PIC 9(2) COMP-5. PROCEDURE DIVISION USING SLEEP-PARM. THREAD-SECTION. DISPLAY "SUBSLEEP ENTRY, WSS-VAR=" WSS-VAR. MOVE TID TO WSS-VAR. MULTIPLY 100 BY SECOND GIVING SLEEPTIME. ACCEPT TIME-TODAY-1 FROM TIME. PERFORM UNTIL DELTATIME >= SLEEPTIME ACCEPT TIME-TODAY-2 FROM TIME COMPUTE DELTATIME = (TIME-TODAY-2 - TIME-TODAY-1) END-PERFORM. DISPLAY "SUBSLEEP EXIT, WSS-VAR=" WSS-VAR. EXIT PROGRAM. END PROGRAM "SUBSLEEP".