APL98 Workshop - Threads in Dyalog APL
Vector 15:2, page 61
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 tnumsis now
⎕TCNUMS tnums- numbers of child threads (direct descendants only)
⎕TNUMSis 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.¶
During development of this workspace, it will occasionally be necessary to stop run-away threads. Function key: F3 has been programmed with:
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:
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:
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:
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]:
Now restart game:
look at the 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:
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¶
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:
Now try: