Skip to content

APL98 Workshop - Threads in Dyalog APL

Vector 15:2, page 61

implementations programming techniques

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 61

Editor’s Note

This set of notes correctly illustrates the syntax and capabilities of the Dyalog interpreter as used in the Rome workshops. There has subsequently been one small change in the usage of the ⎕ functions:

⎕TNUMS tnums is now
⎕TCNUMS tnums - numbers of child threads (direct descendants only)

⎕TNUMS is now niladic, and returns the numbers of all running threads, regardless of parentage.

As this does not significantly affect the Rome examples, it was thought better to leave the notes unmodified.

Things to know about threads:

  • Start a new thread: &
  • My thread number: ⎕TID
  • The thread numbers of children: ⎕TNUMS tnums
  • Kill threads: ⎕TKILL tnums
  • Wait for threads to terminate: ⎕TSYNC tnums
  • Hold a semaphore token: :Hold
  • Display all semaphore tokens: )HOLDS
  • Extended State Indicator Display.
  • Localisation issues.
  • GUI considerations.

Example 1 - Grab: a game for any number of players.

      )load grab

During development of this workspace, it will occasionally be necessary to stop run-away threads. Function key: F3 has been programmed with:

⎕tkill 0        ⍝ Kill background threads.

So, if you get into trouble:

Ctrl+Pause to interrupt foreground thread if there is one;
F3 to kill background threads;
)reset for good measure.

Examine function grab:

      )ed grab

    ∇ grab numb             ⍝ Grab: ...
[1]
[2]   table←numb⍴0          ⍝ Prepare the table.
[3]
[4]   player&¨⍳numb         ⍝ Play!
    ∇
grab[2] Initialises the table to zeros.
grab[4] Starts asynchronous players.

Copy mk1 player:

      )copy grab mk1.player
      )ed player

Mk1.player

    ∇ player me;adj;vic                 ⍝ Play grab.
[1]
[2]   table[me]+←1                      ⍝ Buy into game.
[3]
[4]   adj←1 0 1/3↑(me-2)⌽⍳⍴table        ⍝ Adjacent friends.
[5]
[6]   :Repeat                           ⍝ Endlessly ...
[7]
[8]       vic←adj[?⍴adj]                ⍝ Choose a victim.
[9]
[10]      :If ×table[vic]               ⍝ Worth the hit?
[11]          wait
[12]          table[vic]-←1             ⍝ Take vic's candy.
[13]          wait
[14]          table[me]+←1              ⍝ Keep it for self.
[15]      :End
[16]
[17]      show table                    ⍝ State of play.
[18]
[19]  :End
    ∇

Function show displays the state of play in the session log.

    ∇ show table;tag;tab;sum            ⍝ Display table.
[1]   tag←4 0⍕⎕TID                      ⍝ Thread id:,
[2]   tab←3 0⍕table                     ⍝ Table values,
[3]   sum←⍕+/table                      ⍝ Sum,
[4]   ⎕←tag,': ',tab,' : ',sum          ⍝ ... output to ses
    ∇

Now run the simulation:

      grab 4
   1:   1  0  0  0 : 1
   1:   1  0  0  0 : 1
   2:   1  1  0  0 : 2
   1:   1  1  1  1 : 4
   2:  ¯1  0  1  1 : 1
   3:  ¯1  0  2  1 : 2
   3:  ¯1  0  2  1 : 2
   4:  ¯1  0  2  2 : 3
4:DOMAIN ERROR
player[10] :If ×table[vic]               ⍝ Worth the hit?
          ^

Branching State Indicator

Dyalog’s state indicator looks like a branching tree:

      )si
·   player[11]
&3
·   player[11]
&2
·   player[13]
&1
·   player[10]*
&4

State indicator with name list:

      )sinl
·   player[11]  vic     adj     me
&3
·   player[11]  vic     adj     me
&2
·   player[13]  vic     adj     me
&1
·   player[10]* me      adj     vic
&4

Reset state indicator:

      )reset

Holds - Mk2.player

      )copy grab mk2.player
      )ed player

    ∇ player me;adj;vic                 ⍝ Play grab.
[1]
[2]   table[me]+←1                      ⍝ Buy into game.
[3]
[4]   adj←1 0 1/3↑(me-2)⌽⍳⍴table        ⍝ Adjacent friends.
[5]
[6]   :Repeat                           ⍝ Endlessly ...
[7]
[8]       vic←adj[?⍴adj]                ⍝ Choose a victim.
[9]
[10]      :Hold ⍕vic                    ⍝ Hold him,
[11]
[12]          :If ×table[vic]           ⍝ Worth the hit?
[13]              wait
[14]              table[vic]-←1         ⍝ Take vic's candy.
[15]              wait
[16]              table[me]+←1          ⍝ Keep it for self.
[17]          :End
[18]
[19]      :EndHold                      ⍝ Release victim.
[20]
[21]      show table                    ⍝ Show state of play.
[22]
[23]  :End
    ∇

      grab 4
...
      <F3>
      ⎕tkill 0

Edit player to comment out line[21]:

[21]  ⍝ player[21] show table         ⍝ Show state of play.

Now restart game:

      grab 4

look at the holds:

      )holds
      )holds
      )holds
      )holds

Thread Wars

We can change the spirit of the game by altering the rules slightly.

The following is a ‘Wild West Saloon’ version:

      )copy grab mk3.player
      )ed player

    ∇ player me;vic;rand                ⍝ Play rough.
[1]
[2]   table[me]+←1                      ⍝ Buy into game.
[3]
[4]   rand←{⍵[?⍴⍵]}                     ⍝ ∇ Random choice.
[5]
[6]   :While 1<⍴⎕TNUMS 0                ⍝ Still someone there.
[7]
[8]       vic←rand⍳⍴table               ⍝ Choose random victim.
[9]
[10]      :Hold ⍕vic                    ⍝ Hold him,
[11]
[12]          :If ×table[vic]           ⍝ Worth the hit?
[13]              wait
[14]              table[vic]-←1         ⍝ Take vic's candy.
[15]              wait
[16]              table[me]+←1          ⍝ Keep it for self.
[17]          :End
[18]
[19]      :EndHold                      ⍝ Release victim.
[20]
[21]      ⎕TKILL rand ⎕TNUMS 0          ⍝ Shoot wildly.
[22]
[23]      show table                    ⍝ Show state of play.
[24]
[25]  :End
[26]
[27]  'Thread',⎕TID,'wins.'             ⍝ Gloat.
    ∇

      grab 20

Watch the number of threads decreasing in the session status bar.

Example 2 - Fibonacci Numbers

      )load fibs
      )ed fib1

    ∇ n←fib1 n              ⍝ Fibonacci number
[1]   :If n>1
[2]       n←+/fib1¨n-1 2
[3]   :End
    ∇

      fib1 8                ⍝ 8'th Fibonacci number.
21
      fib1¨⍳10              ⍝ First 10 Fibonacci numbers.
1 1 2 3 5 8 13 21 34 55

Fib2 traces the stages of evaluation:

      )ed fib2

    ∇ n←fib2 n              ⍝ Fibonacci number
[1]   trace n
[2]   :If n>1
[3]       n←+/fib2¨n-1 2
[4]   :End
[5]   trace n
    ∇

Trace shows the thread ID and stack depth:

      )ed trace

    ∇ trace n;tag;dent              ⍝ Trace evaluation
[1]   tag←4↑'&',⍕⎕TID               ⍝ Tag with thread Id.
[2]   dent←,↑(¯2+⍴⎕SI)⍴⊂'·   '      ⍝ Indentation.
[3]   ⎕←tag,' ',dent,⍕n             ⍝ Display.
    ∇

      fib2 5
&0   5
&0   ·   4
&0   ·   ·   3
&0   ·   ·   ·   2
&0   ·   ·   ·   ·   1
&0   ·   ·   ·   ·   1
&0   ·   ·   ·   ·   0
&0   ·   ·   ·   ·   0
&0   ·   ·   ·   1
&0   ·   ·   ·   1
&0   ·   ·   ·   1
&0   ·   ·   2
&0   ·   ·   2
&0   ·   ·   ·   1
&0   ·   ·   ·   1
&0   ·   ·   ·   0
&0   ·   ·   ·   0
&0   ·   ·   1
&0   ·   3
&0   ·   3
&0   ·   ·   2
&0   ·   ·   ·   1
&0   ·   ·   ·   1
&0   ·   ·   ·   0
&0   ·   ·   ·   0
&0   ·   ·   1
&0   ·   ·   1
&0   ·   ·   1
&0   ·   2
&0   5
5

      )ed fib3

    ∇ n←fib3 n              ⍝ Fibonacci number
[1]   trace n
[2]   :If n>1
[3]       n←+/⎕TSYNC fib3&¨n-1 2
[4]   :End
[5]   trace n
    ∇

Fib[3] spawns sub-threads and ⎕TSYNC synchronises the results.

      fib3 5
&0   5
&2   ·   3
&1   ·   4
&4   ·   ·   1
&4   ·   ·   1
&5   ·   ·   3
&6   ·   ·   2
&3   ·   ·   2
&7   ·   ·   ·   1
&8   ·   ·   ·   0
&8   ·   ·   ·   0
&9   ·   ·   ·   2
&7   ·   ·   ·   1
&6   ·   ·   1
&12  ·   ·   ·   ·   0
&12  ·   ·   ·   ·   0
&13  ·   ·   ·   1
&13  ·   ·   ·   1
&11  ·   ·   ·   ·   1
&11  ·   ·   ·   ·   1
&10  ·   ·   ·   1
&10  ·   ·   ·   1
&9   ·   ·   ·   1
&5   ·   ·   2
&1   ·   3
&14  ·   ·   ·   0
&14  ·   ·   ·   0
&3   ·   ·   1
&2   ·   2
&0   5
5

We can generalise this technique with a ‘parallel each’ operator: peach.

      )ed fib4

    ∇ n←fib4 n              ⍝ Fibonacci number
[1]   trace n
[2]   :If n>1
[3]       n←+/fib4 peach n-1 2
[4]   :End
[5]   trace n
    ∇

      )ed peach

    ∇ rslt←(func peach)argt
[1]   rslt←⎕TSYNC func&¨argt
    ∇

… or the dynamic operator equivalent:

      peach←{⎕TSYNC ⍺⍺&¨⍵}

Fib5 contains an error if n is 0:

    ∇ n←fib5 n              ⍝ Fibonacci number
[1]   :If n>1
[2]       n←+/fib5 peach n-1 2
[3]   :Else
[4]       ∘
[5]   :End
    ∇

      fib5
4:SYNTAX ERROR
fib5[4] ∘
        ^
      )si
·   ·   peach[1]
·   ·   fib5[2]
·   &3
·   ·   fib5[4]*
·   &4
·   peach[1]
·   fib5[2]
&1
·   peach[1]
·   fib5[2]
&2
peach[1]
fib5[2]
      )sinl
·   ·   peach[1]        rslt    func    argt
·   ·   fib5[2] n       n
·   &3
·   ·   fib5[4]*        n       n
·   &4
·   peach[1]    rslt    func    argt
·   fib5[2]     n       n
&1
·   peach[1]    argt    func    rslt
·   fib5[2]     n       n
&2
peach[1]        argt    func    rslt
fib5[2] n       n

Example 3 - GUI applications

      )load gui
      form 5 5

Click on several of the buttons.

Notice that each button starts counting only when the previous one has finished.

      )ed form callback

    ∇ form rc;fm;⎕IO        ⍝ Counting buttons
[1]   ⎕IO←0
[2]   'fm'⎕WC'form'
[3]   {
[4]       size←100÷rc
[5]       posn←⍵×size
[6]       name←'fm.btn',⍕rc⊥⍵
[7]       evnt←'Event' 'Select' 'count'
[8]       name ⎕WC'button' '0'posn size evnt
[9]   }¨⍳rc
[10]  ⎕DQ'fm'
    ∇

    ∇ count msg;i;obj       ⍝ counting callback.
[1]   obj←⊃msg
[2]   :For i :In 0,(⍳999),0
[3]       :If ×⎕NC obj ⋄ obj ⎕WS'Caption'(⍕i)
[4]       :Else
[5]           :Leave
[6]       :End
[7]   :End
    ∇

We could make the buttons count in separate threads, by having callback function count spawn an asynchronous sub-function.

There is a shorthand way of doing this by appending & to the name of the callback function when setting the Event.

Change line form[7] to the following and try again:

[7]       evnt←'Event' 'Select' 'count&'

Now try:

      form&5 5
      )sinl