Multi-threaded programs can share the same data and by default the switch from processing one thread to another can take place at any time. Sometimes it is necessary to protect shared data by making sure that a calculation in one thread is not interrupted by processing (that uses or changes the shared data) in another thread. One simple example of this is where two threads READ from (or WRITE to) the same Fortran unit. Another is where one thread must update a data structure that is used by another thread. The simplest way to achieve this is to use the LOCK statement:
LOCK
<some sensitive statements>
END LOCK
The system prevents a swap to another thread whilst the statements within a LOCK block are being executed. Once a block has been entered, other threads are forced to wait until the block has been completed.
In some cases it is useful to apply a more selective locking mechanism. A lock can be applied to any .NET object (typically stored in a FTN95 module). The object must be created (using NEW@) before a LOCK statement is applied:
OBJECT("System.Object") MY_OBJ
MY_OBJ=NEW@("System.Object")
...
LOCK(MY_OBJ)
! Some sensitive statements...
END LOCK
This form of the LOCK statement only operates in relation to other locked blocks (in a different thread) that refer to the same object.
Here are some rules for locking:
1. One lock has no effect upon other locks in the same thread.
2. Using the same lock a second time (i.e. nesting) has no effect.
3. If a locked block is exited prematurely (because an exception is raised) the corresponding lock is removed.
4. You must not enter or exit a locked block via a GOTO statement.
Programs with multiple threads can contain bugs that arise from the interaction of the various threads. In particular, if different threads use and change the same data, a program can produce results that vary from one run to another (because the results depend upon the order in which the threads have been executed). A temporary LOCK wrapped around a significant block of code can be used to identify a threading bug of this kind. The temporary lock forces the code to run serially thus preventing the possibility of a threading bug occurring in this part of the code.
It is also possible to synchronise threads at a lower level by directly calling methods in the System.Threading.Monitor class. These methods can be used to develop a more elaborate system of thread interaction. For example, you could perform a new task whilst waiting for data for the current task to become available.