Globals and the Order of Execution

It is important to recognise that any reference or assignment to a global or semi-global object (including GUI objects) is inherently dangerous (that is, a source of programming error) if more than one thread is running. Worse still, programming errors of this sort may not become apparent during testing because they are dependent upon random timing differences. Consider the following example:

      BUG;SEMI_GLOBAL
[1]    SEMI_GLOBAL0
[2]    FOO& 1
[3]    GOO& 1
     

      FOO
[1]    :If SEMI_GLOBAL=0
[2]        DO_SOMETHING SEMI_GLOBAL
[3]    :Else
[4]        DO_SOMETHING_ELSE SEMI_GLOBAL
[5]    :EndIf
     

      GOO
[1]    SEMI_GLOBAL1
     

In this example, it is formally impossible to predict in which order APL will execute statements in BUG, FOO or GOO from BUG[2] onwards. For example, the actual sequence of execution may be:

      BUG[1]  BUG[2]  FOO[1]  FOO[2] 
               DO_SOMETHING[1]

or

      BUG[1]  BUG[2]  BUG[3]  GOO[1] 
               FOO[1]  FOO[2]  FOO[3] 
               FOO[4]  DO_SOMETHING_ELSE[1]

This is because APL may switch from one thread to another between any two lines in a defined function. In practice, because APL gives each thread a significant time-slice, it is likely to execute many lines, maybe even hundreds of lines, in one thread before switching to another. However, you must not rely on this; thread-switching may occur at any time between lines in a defined function.

Secondly, consider the possibility that APL switches from the FOO thread to the GOO thread after FOO[1]. If this happens, the value of SEMI_GLOBAL passed to DO_SOMETHING will be 1 and not 0. Here is another source of error.

In this case, there are two ways to resolve the problem. To ensure that the value of SEMI_GLOBAL remains the same from FOO[1] to FOO[2], you can use diamonds instead of separate statements. For example:

      :If SEMI_GLOBAL=0  DO_SOMETHING SEMI_GLOBAL

Even better, although less efficient, you can use :Hold to synchronise access to the variable. For example:

       FOO
[1]    :Hold 'SEMI_GLOBAL'
[2]        :If SEMI_GLOBAL=0
[3]            DO_SOMETHING SEMI_GLOBAL
[4]        :Else
[5]            DO_SOMETHING_ELSE SEMI_GLOBAL
[6]        :EndIf
[7]    :EndHold
     

      GOO
[1]    :Hold 'SEMI_GLOBAL'
[2]        SEMI_GLOBAL1
[3]    :EndHold
     

Now, although you still cannot be sure which of FOO and GOO will run first, you can be sure that SEMI_GLOBAL will not change (because GOO cuts in) within FOO.

Note that the string used as the argument to :Hold is completely arbitrary, so long as threads competing for the same resource use the same string.

Warning

These types of problems are inherent in all multithreading programming languages, and not just with Dyalog APL. If you want to take advantage of the additional power provided by multithreading, it is advisable to think carefully about the potential interaction between different threads.