PreviousSynchronizing Execution and Resolving Contention Multi-threading Compiler DirectivesNext

Chapter 3: Writing Multi-threaded Applications

When you create a multi-threaded application you need to consider how each program in the application should operate: as a standard COBOL program, as a serial program, or as a reentrant program. Of the three types of program, programs compiled as REENTRANT(1) programs need most attention.

You also to need to consider how to:

You should also be aware of common problems with multi-threaded programming .

3.1 Run-time System for Multi-threaded Applications

The run-time system for multi-threaded applications needs to handle both multi-threaded and single-threaded applications. As a result, the multi-threaded run-time system incurs many of the synchronization costs that a standard multi-threaded COBOL application would. Further, most of these costs are incurred regardless of whether the application is multi-threaded or single-threaded. Because of this, and in order to enable single-threaded applications to run at maximum speed, Micro Focus provides two separate run-time systems: a multi-threaded run-time system that supports all applications, and a single-threaded run-time system that supports only single-threaded applications. You can choose between these run-time systems either when you run your application or when you link your application.

The files you need to ship with an application will depend in some cases on whether your application has been linked to the single-threaded or multi-threaded run-time system. See the help topic Determining the Run-time Files to Ship for details.

3.2 Considerations When Creating Reentrant Programs

Not every program can be compiled as reentrant. Use of some COBOL features will preclude reentrant compilation; most of these COBOL features are obsolete or archaic in ANSI Standard COBOL and their use should be discontinued. Programs that use the following features cannot be compiled with the REENTRANT directive:

If you are creating a reentrant program, there are also other restrictions you should be aware of:

The following performance and resource penalties are incurred in reentrant COBOL programs within a multi-threaded application:

3.3 Multi-threading Library Routines

Call-by-name library routines provide a programming interface through which you can implement and control multi-threading in your applications. Library routines are provided for:

3.3.1 Thread-control Routines

The library routines for controlling threads can be used to implement threads that wait for another thread to terminate and then pick up a return value.

Any thread created using these library routines must:

A thread might not have been created by the thread-control routines. However, such a thread might still be known to the run-time system by the thread's use of run-time system services. Such a thread can be accessed via the thread-control library routines; it can use:

In a mixed-language environment, any thread known to the run-time system from its use of the run-time system, but not created by the run-time system or any of the thread control routines, must do one of the following:

An application can check that the run-time system used by a program supports the thread-control routines by calling either CBL_GET_OS_INFO or CBL_THREAD_SELF.

Example - Checking Which Run-time System a Program Supports
 call "CBL_THREAD_SELF" using thread-id
     on exeception
     *> no cbl_thread routines support
 end-call
 if  return-code = 1008
     *> running in a single-threaded-only rts
 end-if

3.3.2 Thread-synchronization Routines

Net Express provides four types of synchronization object: monitors, semaphores, mutexes, and events. Control of these synchronization objects is provided by COBOL syntax, and also by call-by-name library routines.

3.3.3 Routines for Handling Thread-specific Data

You can create dynamic data whose lifetime is that of the creating thread, or selective or external thread-local data, using the run-time library routine CBL_ALLOC_THREAD_MEM and the library routines CBL_TSTORE_n.

3.3.4 Initializing Applications

The initialization of multi-threaded applications needs some care. First, as good programming practice, the application should determine if it is running with a run-time system that supports multi-threading. See the following example.

Example - Checking the Run-time System
 Working-Storage Section.
 01 thread-id     usage pointer.

*> Initialization code executed while in
*> single-threaded mode to check if the 
*> run-time system supports multi-threading 
*> or the CBL_THREAD_ routines.
 call 'CBL_THREAD_SELF' using thread-id
 on exception
*> No cbl_thread routines support
 end-call
 if return-code = 1008
    *> Running in a single-threaded rts
 end-if

This example makes use of the run-time library routine CBL_THREAD_SELF. While many facilities of multi-threading are accessible through the COBOL syntax, all facilities are also accessible through COBOL run-time library routines. The run-time library routines also offer advanced multi-threading programming capabilities that are unavailable through the COBOL syntax.

After the application has determined that the run-time system supports multi-threading, all synchronization primitives should be be initialized using the appropriate OPEN syntax. It is easiest to do this as part of the main program, before any other threads are created within the application. However, if this not possible, either because of the way the application is designed, or because of modularity or mixed-language considerations, Micro Focus have provided a single pre-initialized mutex per program. This mutex is accessed using the CBL_THREAD_PROG_LOCK and CBL_THREAD_PROG_UNLOCK run-time library routines. Using these routines, it is possible to ensure that any handles for program -local synchronization primitives are initialized once (and only once) during the execution of the application.

Example - Thread Locking Using Library Routines

The following code illustrates a sample use of these routines for program initialization while running in multi-threaded mode:

 Working-Storage Section.
 01 first-flag         pic x comp-x value 1.
	   88 first-time      value 1.
	   88 not-first-time  value 0.
	 
*> Initialization code executed while in 
*> multi-threaded mode. Ensures that program 
*> local data is initialized properly.

 if first-time then
	    call 'CBL_THREAD_PROG_LOCK'
	    if first-time then
*> Initialize program local data and synchronization
*> objects
			  ...
		   set not-first-time   to true
	end-if
	call 'CBL_THREAD_PROG_UNLOCK'
 end-if

Note the double checking of the first-time level-88 data item. This is a good optimization technique in multi-threaded applications. The idea of the optimization is to avoid the overhead of locking a mutex if the intended action has already been performed. In this case, if multiple threads enter the program before it is properly initialized, they will all find that first-time is true and so issue a call to CBL_THREAD_PROG_LOCK. However, only one will acquire the lock while first-time is still true. This is the thread that will do the initialization.

After the initialization is completed, and while the initializing thread still owns the lock, the first-time flag is set to false. All successive threads acquiring the lock can see that the initialization has already been done, as first-time is now false, and unlock the program immediately. Any following threads that enter after initialization will also see the first-time flag is false and will not attempt to acquire the program lock (and so reduce program entry overhead).

The flag that specifies whether initialization has occurred should as simple as possible (that is, a single byte data item).

3.4 Manipulating Threads

The multi-threaded run-time system supports threads created by the run-time system itself, via the START verb or the CBL_THREAD_n library routines, and also other-language threads created directly by the operating system. Since the COBOL syntax and the CBL_THREAD_n routines are portable, these are the preferred methods of thread manipulation. Furthermore, the CBL_THREAD_n routines can also be called from programs written in other languages, allowing any multi-threaded application to take full advantage of the advanced facilities provided by the run-time system.

3.4.1 Thread Handles

In general, threads are identified to the run-time system by thread handles. Thread handles are provided to:

The thread handle uniquely identifies the thread to the various multi-threading COBOL verbs or to CBL_THREAD_n routines. With reasonable care, thread handles can be used by any of the active threads in the run-unit, not just the creator of a thread. It is important to remember that the lifetime of a thread and that of its thread handle might not be the same. Depending on how a thread was created and what operations have been done on it, a thread handle could still be valid even if the thread has terminated. In order to enable the system to recover the resources associated with the handle, it is up to the user to explicitly detach the handle for a thread.

A handle can become detached from its thread in one of several ways. When a thread is created with the START verb or CBL_THREAD_CREATE routine, its handle is, by default, created as detached. When another-language thread becomes known to the run-time system, its handle is automatically and always created as detached. A non-detached handle can be detached with a call to CBL_THREAD_DETACH. In any case, a detached handle must always be used with care, as when the thread terminates the handle immediately becomes invalid. If a thread with a non-detached handle has already terminated, then a call to CBL_THREAD_DETACH with that thread handle will immediately cause the handle to become invalid.

A non-detached handle is useful for retrieval of return values (via the WAIT verb or CBL_THREAD_WAIT routine), and for inspection of thread identification data (via the CBL_THREAD_IDDATA_GET routine) after a thread has potentially terminated. The START verb provides a way of identifying a newly created thread by returning a non-detached thread handle. The CBL_THREAD_CREATE routine provides a flag to indicate that the thread should be created and a non-detached handle returned.

3.4.2 Creating and Terminating a Thread

Threads are either created by the:

Threads created by the first two methods are called COBOL threads. Threads created directly by the operating system are called other-language threads.

As the creation and manipulation of COBOL threads is portable, and the preferred method, we will only deal with them here.

The starting point for a thread must be either a non-nested Program-Id, a COBOL entry-point or a an externally visible routine written in another language, such as C. It must not be a nested program, a section or paragraph name.

The name of the starting point can be specified as a text string. The overhead in using a text string to find the entry point is equivalent to resolving the verb CALL identifier. It is possible to avoid this overhead by using a procedure pointer as the object of a START verb.

The starting point for every created thread is provided with only one parameter. If the BY CONTENT phrase is used in passing this parameter, a copy of it is made by the system before the thread is created, leaving the calling thread free to modify the original parameter upon return from the START verb.

Creation of a thread with a non-detached handle (via the IDENTIFIED BY clause) enables the return value to be obtained later by the WAIT verb. After the WAIT verb has completed the specified thread handle becomes invalid and all resources associated with the subject thread are released.

A STOP RUN RETURNING statement in the created thread does not end the run-unit; it simply provides a return value and terminates the thread. It is equivalent to:

call 'CBL_THREAD_EXIT' using by value address of thread-parm.

STOP RUN in other-language threads or the main thread of a run-unit not created by CBL_THREAD_CREATE will wait for all active COBOL threads to finish and then terminate the run-unit.

Return values from threads are always pointers. This allows you to return both simple and complex data structures.

A thread can terminate itself with STOP RUN or a call to CBL_THREAD_EXIT. A thread also terminates normally when the starting point program exits via EXIT PROGRAM or GOBACK. It is also sometimes useful for another thread to cause a COBOL thread to be terminated. The CBL_THREAD_KILL routine achieves this.

Example - Creating and Terminating a Thread
$set reentrant 

 Data Division.
 Working-Storage Section.
 01 thread-handle          usage pointer.
 01 thread-return          usage pointer.
 Linkage Section.
 01 thread-parm           picture x(32).
 01 thread-return-record      picture x(32).
 Procedure Division.
*> Starting point
 call 'CBL_THREAD_CREATE'	
           using    'CREATED'
                    'This is a 32 character parameter'
           by value 0  *> Optional parameter size
                    1   *> Flag to create non-detached
                    0  *> Default priority
                    0  *> Default stack
           by reference thread-handle
 if return-code = 0 
     call 'CBL_THREAD_WAIT'	using 
                            by value thread-handle
                            by reference thread-return
     set address of thread-return-record 
         to thread-return
     display thread-return-record
 end-if
 stop run.
 Entry "CREATED" using thread-parm.
 display thread-parm
 stop run returning address of thread-parm.

This application simply creates a thread with the starting point CREATED. The thread displays its parameter and returns the address of that parameter for use in the parent thread. The STOP RUN RETURNING in the CREATED thread does not end the run-unit, instead it simply provides a return value and terminates the thread. It is equivalent to:

call 'CBL_THREAD_EXIT' using by value address of thread-parm.

3.4.3 Canceling a Thread

Threads created with CBL_THREAD_CREATE can be canceled with the CBL_THREAD_KILL routine. You cannot use CBL_THREAD_KILL to kill threads created in other ways. This call causes immediate, abnormal termination of a COBOL thread but, in general, it should not be used as part of general application thread control. The main reason for this is that some user and system synchronization resources will not be properly unlocked and/or freed when the thread terminates. This can affect the synchronization within the user application and the run-time system, which can cause serious problems with the application.

CBL_THREAD_KILL can reasonably be used within a critical error handler in the main thread. In this error handler, thread handles can be acquired by way of the CBL_THREAD_LIST_n routines, all threads can be canceled and the application can then exit via STOP RUN. This is less dangerous than the random use of CBL_THREAD_KILL as it minimizes the chance of needing a locked synchronization primitive but there is still a possibility of file corruption or deadlock during run-unit termination.

In any case, most applications should avoid using CBL_THREAD_KILL. This can be achieved by creating thread identification data (through the CBL_THREAD_IDDATA_ALLOC routine) that has a terminate flag in it. That data can then be polled in each thread at a level where no locks are held. If the terminate flag is set, then the polling thread can exit gracefully.

Example - Creating Thread Identification Data and Terminate Flags
**************************** MAINPROG.CBL ****************************
 identification division.
 program-id. mainprog.

 Data Division.
 Local-Storage Section.
 01  iddata-ptr              usage pointer.
 01  sub-iddata-ptr          usage pointer.
 01  sub-handle              usage pointer.
Linkage Section.
 01  iddata-record.
     05  iddata-name         pic x(20).
     05  iddata-term         pic x comp-x value 0.

 Procedure Division.
*>
*> Establish identification data - don't provide 
*> initialization data when it is allocated, instead
*> initialize it after the pointer is retrieved.
 
 call 'CBL_THREAD_IDDATA_ALLOC' using 
                                by value zero
                            length of iddata-record
 call 'CBL_THREAD_IDDATA_GET'   using iddata-ptr
                             by value 0
 set address of iddata-record to iddata-ptr
 move 'main' to iddata-name
*>
*> Create sub-thread
*>
*> Starting point
 call 'CBL_THREAD_CREATE' using    'SUBPROG '  
                          by value 0   *> No parameters
                                   0   *> Optional      parameter size
                                   0   *> Flag to create detached
                                   0   *> Default priority
                                   0   *> Default stack
                      by reference sub-handle
 if  return-code not = 0 
     display 'unable to create thread'
     stop run
 end-if
*>
*> Wait until child creates its iddata 
*> and then flag termination
*>
   set sub-iddata-ptr to NULL
   perform until 1 = 0
       call 'CBL_THREAD_IDDATA_GET'  
                using    sub-iddata-ptr
                by value sub-handle
       if  sub-iddata-ptr not = null
           exit perform
       end-if
       call 'CBL_THREAD_YIELD'
   end-perform
   set address of iddata-record to sub-iddata-ptr
   move 1 to iddata-term
*>
*> Wait till the child resumes this thread
*>
   call 'CBL_THREAD_SUSPEND' using by value 0 
   display 'All synchronization is complete on RTS termination'
   stop run.
end program mainprog.
***************************** SUBPROG.CBL ****************************
 identification division.
 program-id. subprog.

 Data Division.
 Working-Storage Section.
 01 sub-iddata.
    05  sub-name             pic x(20) value 'sub'.
    05  sub-term             pic x comp-x value 0.
Local-Storage Section.
 01  iddata-ptr              usage pointer.
 01  thread-handle           usage pointer.
 01  thread-state            pic x(4) comp-x.
 01  parent-handle           usage pointer.
Linkage Section.
 01  iddata-record.
     05  iddata-name         pic x(20).
     05  iddata-term         pic x comp-x value 0.
 Procedure Division.
*>
*> Establish identification data - provide initialization data
 call 'CBL_THREAD_IDDATA_ALLOC' 
                         using sub-iddata
                         by value length of sub-iddata
*>
*> Find our parent thread and resume him
*>
 call 'CBL_THREAD_LIST_START'   using thread-handle
                                      thread-state
                                      iddata-ptr
 set parent-handle to NULL
 perform until thread-handle = null
     or return-code not = 0
     if iddata-ptr not = null
         set address of iddata-record to iddata-ptr
         if  iddata-name = 'main'
             set parent-handle to thread-handle
             exit perform
         end-if
     end-if
     call 'CBL_THREAD_LIST_NEXT' using thread-handle
                                       thread-state
                                       iddata-ptr
 end-perform
 call 'CBL_THREAD_LIST_END'
 if  parent-handle = NULL
     display 'synchronization error'
     stop run
 end-if
 call 'CBL_THREAD_RESUME' using by value parent-handle
 call 'CBL_THREAD_IDDATA_GET'   using iddata-ptr
                                by value 0
 set address of iddata-record to iddata-ptr
 perform until iddata-term = 1
     call 'CBL_THREAD_YIELD'
 end-perform
 exit program.
 end program subprog.

This rather lengthy example doesn't actually do anything but establish handshaking for thread and application termination. Before going into any discussion on it, it is worth noting that this kind of handshaking could have been accomplished more easily by passing the main thread's handle into the child as a parameter. This would have avoided the need to rely on identification data or to step through the thread list.

First, notice the two different ways of creating thread identification data. The first way, in the parent thread, creates the data uninitialized, retrieves the iddata pointer and then initializes the area of memory that was allocated. The second, in the child, provides initialization data to eliminate any possible execution window between iddata allocation and iddata initialization by the application. The method used in your application will depend on what level of contention you expect on identification data.

Also, notice the loop in the parent that waits for the child to create its thread identification data and the loop in the child that waits for the parent to flag termination. The call to CBL_THREAD_YIELD prevents these loops from being hard busy waits, but would have been better coded using an event or condition synchronization object or using CBL_THREAD_SUSPEND and CBL_THREAD_RESUME.

Finally, notice the use of the CBL_THREAD_LIST_ API. This API allows a thread to step through all threads known to the RTS, obtaining the thread handle, thread state and identification data pointer. In this example we relied only on the handle and identification data pointer but the state information can also be useful, as it lets the calling thread know if the subject thread is detached, suspended or an other-language thread.

It is important to realize that whenever the CBL_THREAD_LIST API is used, the using thread has limitations on further CBL_THREAD_ calls and all other threads are completely locked out from using any of these calls as well as quite a few of the other RTS calls. For this reason, it makes sense to keep the amount of code executed while stepping the list to a minimum and certainly avoiding file or user I/O while the list is locked. These restrictions are removed only after the stepping is terminated by a call to CBL_THREAD_LIST_END

3.4.4 Suspending a Thread

In many multi-threaded applications it is not uncommon for a thread to 'run out of work' but not need to terminate just yet. In a client-server architecture, for example, the main service thread could be polling an input queue for requests and if it finds none then it makes sense to allow another process or thread to have the CPU before polling the input queue again. This is simply done by calling CBL_THREAD_YIELD which yields the processor to some other thread in the application (or some other thread in another process, depending on the operating system).

Another possibility is that a thread needs to yield the CPU indefinitely and obtain it only when some sort of event has definitely happened. CBL_THREAD_SUSPEND allows suspension of the calling thread until some other thread does a CBL_THREAD_RESUME using the thread handle for the suspended thread. A thread can call CBL_THREAD_RESUME one or more times before the targeted thread suspends itself and, in this case, the call to CBL_THREAD_SUSPEND returns to the calling thread immediately and does not give up ownership of the CPU. This operation is very similar to that of a counting semaphore; in fact, the Producer-Consumer problem can be solved using only the CBL_THREAD_SUSPEND and CBL_THREAD_RESUME routines.

3.4.5 Identifying a Thread

It is sometimes useful for a thread to distinguish itself from any other created threads in an application. For example, if two threads in a four thread application establish a producer-consumer relationship, it can be useful for the two cooperating threads to find out what each other's thread handles are. Once these handles are obtained, all synchronization can be done using only the CBL_THREAD_SUSPEND and CBL_THREAD_RESUME calls. If each thread in the application creates a name for itself (and the name relates to its functionality) and associates the name with its thread handle, then the cooperating threads can scan the thread name list and find each other's handle.

Another possible use for associating globally accessible data to each thread and its handle is to hold a termination flag, obviating the possible need to use CBL_THREAD_KILL. Each thread in the application can poll its termination flag to check for a termination request. The terminating thread can then make sure no locks are held and no synchronization actions are required before terminating normally.

Globally accessible data for a thread is associated with the thread handle by the CBL_THREAD_IDDATA_ALLOC routine executed within the thread. This data is retrieved by the CBL_THREAD_IDDATA_GET routine, if the thread handle is already known, or by the CBL_THREAD_LIST_n routines if the thread handle is not yet known.

Example - Associating Globally Accessible Data With a Thread Handle

The following example shows how to associate globally accessible data with the thread handle by using the CBL_THREAD_IDDATA_ALLOC call executed within the thread. The data is retrieved by the CBL_THREAD_IDDATA_GET call, if the thread handle is already known, or by the CBL_THREAD_LIST_n routines if the thread handle is not yet known.

**************************** MAINPROG.CBL ****************************
 identification division.
 program-id. mainprog.

 Data Division.
 Local-Storage Section.
 01 iddata-ptr       usage pointer.
 01 sub-iddata-ptr   usage pointer.
 01 sub-handle       usage thread-pointer.
 Linkage Section.
  01 iddata-record.
     05 iddata-name  pic x(20).
   05 iddata-term    pic x comp-x value 0.

 Procedure Division.
*>
*> Establish identification data - don't provide 
*> initialization data when it is allocated, instead
*> initialize it after the pointer is retrieved.
*>
 call 'CBL_THREAD_IDDATA_ALLOC' using 
                                by value zero
                                length of iddata-record
 call 'CBL_THREAD_IDDATA_GET'   using iddata-ptr
                                by value 0
 set address of iddata-record to iddata-ptr
 move 'main' to iddata-name
*>
*> Create sub-thread
*>
  start 'SUBPROG '  identified by sub-handle
*>
*> Wait until child creates its iddata and then flag 
*> termination
*>
 set sub-iddata-ptr to NULL
 perform until 1 = 0
     call 'CBL_THREAD_IDDATA_GET' using sub-iddata-ptr
                                  by value sub-handle
     if sub-iddata-ptr not = null
         exit perform
     end-if
     call 'CBL_THREAD_YIELD'
 end-perform
 set address of iddata-record to sub-iddata-ptr
 move 1 to iddata-term
*>
*> Wait until the child resumes this thread
*>
 call 'CBL_THREAD_SUSPEND' using by value 0 
 display 'All synchronization is complete on RTS termination'
 wait for sub-handle	*> clear up thread's resources
 stop run.
 end program mainprog.
***************************** SUBPROG.CBL ****************************
 identification division.
 program-id. subprog.

 Data Division.
 Working-Storage Section.
 01 sub-iddata.
    05 sub-name				pic x(20) value 'sub'.
    05 sub-term				pic x comp-x value 0.
 Local-Storage Section.
 01 iddata-ptr				usage pointer.
 01 thread-handle	usage pointer.
 01 thread-state		pic x(4) comp-x.
 01 parent-handle	usage pointer.
 Linkage Section.
 01 iddata-record.
    05 iddata-name   pic x(20).
    05 iddata-term   pic x comp-x value 0.
Procedure Division.
*>
*> Establish identification data - provide 
*> initialization data
 call 'CBL_THREAD_IDDATA_ALLOC'
                      using sub-iddata
                      by value length of sub-iddata
*>
*> Find our parent thread and resume him
*>
 call 'CBL_THREAD_LIST_START'		
                   using thread-handle
                         thread-state
                         iddata-ptr
 set parent-handle to NULL
 perform until thread-handle = null 
     or return-code not = 0
     if iddata-ptr not = null
         set address of iddata-record  to iddata-ptr
         if iddata-name = 'main'
             set parent-handle to thread-handle
             exit perform
        end-if
     end-if
     call 'CBL_THREAD_LIST_NEXT' using thread-handle
                                       thread-state
                                       iddata-ptr
 end-perform
 call 'CBL_THREAD_LIST_END'
 if parent-handle = NULL
     display 'synchronization error'
 stop run
 end-if
 call 'CBL_THREAD_RESUME' using by value parent-handle
 call 'CBL_THREAD_IDDATA_GET' using iddata-ptr
                              by value 0
 set address of iddata-record to iddata-ptr
 perform until iddata-term = 1
     call 'CBL_THREAD_YIELD'
 end-perform
 exit program.
 end program subprog. 

This example establishes handshaking for thread and application termination. Note that this kind of handshaking could have been accomplished more easily by passing the handle of the main thread into the child as a parameter. This would have avoided the need to rely on identification data or to step through the thread list.

The example shows two different ways of creating thread identification data. The first , in the parent thread, creates the data uninitialized, retrieves the identification data pointer and then initializes the area of memory that was allocated. The second, in the child, provides initialization data to eliminate any possible execution window between identification data allocation and identification data initialization by the application. The method used in your application will depend on what level of contention you expect on identification data.

Note the loop in the parent that waits for the child to create its thread identification data and the loop in the child that waits for the parent to flag termination. The call to CBL_THREAD_YIELD prevents these loops from being hard busy waits, but would have been better coded using an event or condition synchronization object or using CBL_THREAD_SUSPEND and CBL_THREAD_RESUME.

Finally, note the use of the CBL_THREAD_LIST_n routines. These allow a thread to step through all threads known to the RTS, obtaining the thread handle, thread state and identification data pointer. In this example we relied only on the handle and identification data pointer but the state information can also be useful, as it lets the calling thread know if the subject thread is detached, suspended or an other-language thread.

It is important to realize that whenever the CBL_THREAD_LIST_n routines are used, the thread using them has limitations on further CBL_THREAD_n calls, and all other threads are completely locked out from using any of these calls as well as some of the other run-time system calls. For this reason, it makes sense to keep the amount of code executed while stepping the list to a minimum, and avoid file or user I/O while the list is locked. These restrictions are removed only after stepping the list is terminated by a call to CBL_THREAD_LIST_END.

3.4.6 Threads in Other Languages

In a mixed language environment, threads created directly by operating system facilities and not by the START verb or the CBL_THREAD_CREATE routine have several restrictions and requirements on them:


Note: All the multi-threading functions supported by the run-time system detect handles created by threads in other languages, and reports invalid use of them. It is, however, entirely up to the application to ensure that run-time system resources are freed for any thread that has executed any COBOL or run-time system code in the course of its existence (as detailed above).


3.5 Canceling Called Programs

As the CANCEL operation takes a finite time to complete it is possible for a thread to enter the canceled module before the CANCEL operation has finished. You are advised, therefore, not to use the CANCEL statement in multi-threaded applications.

3.6 Optimizations and Programming Tips

To create optimized and efficient programs, note the following:

The Compiler directives available for multi-threaded applications can affect the performance of your multi-threaded and single-threaded applications.


Copyright © 2000 MERANT International Limited. All rights reserved.
This document and the proprietary marks and names used herein are protected by international law.

PreviousSynchronizing Execution and Resolving Contention Multi-threading Compiler DirectivesNext