# Threads operations

This chapter covers the threads facilities builtin in the interpreter. The thread subsystem allows for the execution of concurrent forms with an automatic synchronization mechanism. Designing a good program with concurrent execution is a difficult task. It takes a while to get used with the various synchronization mechanisms which ensure a safe execution, that is no race condition or dead lock. Fortunately, provides some unique features that should ease such design.

## Normal and daemon threads

The interpreter supports two types of threads, called normal and daemon threads. A normal thread is started with the reserved keyword `launch`. A daemon thread is started with the reserved keyword `daemon`. The difference between a normal thread and a daemon thread is only in the termination of the interpreter. An program is completed when all normal threads have terminated. This means that the master thread (i.e the first thread) is suspended until all normal threads have been executed. With daemon threads, the master thread terminates even if some daemon threads are still running.

### Starting a normal thread

A normal thread is started with the reserved keyword `launch`. The form to execute in a thread is the argument. The simplest thread to execute is the `nil` thread.

launch (nil)

Even the `nil` thread does nothing in term of computation, it does a lot of things internally by turning on the shared objects subsystem.

### Thread object and result

When a thread terminate, the thread object holds the result of the last executed form. The thread object is returned by the `launch` or `daemon` command. The `thread-p` predicates returns `true` if the object is a thread descriptor. The thread type can be check with the `normal-p` or `daemon-p` predicates.

const thr (launch (nil)) println (thread-p thr) # true println (thr:normal-p) # true

The member data `result` of the thread object holds the result of the thread. Although the result can be accessed at any time, the returned value will be nil until the thread as completed its execution.

const thr (launch (nil)) println (thr:result) # nilp

Although the engine will ensure that the result is `nil` until the thread has completed its execution, it does not mean that it is a reliable approach to test until the result is not `nil`. The engine provides various mechanisms to synchronize a thread and eventually wait for its completion.

## Shared objects

The whole purpose of using a multi-threaded environment is to provide a concurrent execution with some shared variables. Although, several threads can execute concurrently without sharing data, the most common situation is that one or more global variable are accessed -- and even changed -- by one or more threads. Various scenarios are possible. For example, a variable is changed by one thread, the other thread just read its value. Another scenario is one read, multiple write, or even more complicated, multiple read and multiple write. In any case, the interpreter subsystem must ensure that each objects are in a good state when such operation do occur.

The engine provides an automatic synchronization mechanism for global objects, where only one thread can modify an object, but several thread can read it. This mechanism known as read-write locking guarantees that there is only one writer, but eventually multiple reader. When a thread start to modify an object, no other thread are allowed to read or write this object until the transaction has been completed. On the opposite, no thread is allowed to change (i.e. write) an object, until all thread which access (i.e. read) the object value have completed the transaction. Because a context switch can occur at any time, the object read-write locking will ensure a safe protection during each concurrent access.

Shared objects can be very complicated to detect. For example, if a vector is shared by various threads, the engine will make sure that all vector objects are also shared. A closed variable in a lambda or gamma expression is another example of potential shared object. Executing such lambda form in a thread will automatically mark the closed variables as shared objects.

### Shared protection access

We illustrate the previous discussion with an interesting example and some variations around it. Let's consider a form which increase an integer object and another form which decrease the same integer object. If the integer is initialized to 0, and the two forms run in two separate threads, we might expect to see the value bounded by the time allocated for each thread. In other word, this simple example is a very good illustration of your machine scheduler.

# shared variable access const var 0 # increase method const incr nil (while true (println "increase: " (var:= (+ var 1)))) # decrease method const decr nil (while true (println "decrease: " (var:= (- var 1))))

# start both threads launch (decr) launch (incr)

In the previous example, `var` is initialized to 0. The `incr` thread increments `var` while the `decr` thread decrements `var`. Depending on the operating system, the result stays bounded within a certain range. The previous example can be changed by using the main thread or a third thread to print the variable value. The end result is the same, except that there is more threads competing for the shared variable.

# shared variable access const var 0

# incrementer, decrementer and printer const incr nil (while true (var:= (+ var 1))) const decr nil (while true (var:= (- var 1))) const prtv nil (while true (println "value = " var)

# start all threads launch (decr) launch (incr) launch (prtv)

## Synchronization

Although, provides an automatic synchronization mechanism for reading or writing an object, it is sometimes necessary to control the execution flow. There are basically two techniques to do so. First, protect a form from being executed by several threads. Second, wait for one or several threads to complete their task before going to the next execution step.

### Form synchronization

The reserved keyword `sync` can be used to synchronize a form. When a form, is synchronized, the engine guarantees that only one thread will execute this form.

const print-message (code mesg) ( sync { errorln "error : " code errorln "message: " mesg } )

The previous example create a gamma expression which make sure that both the error code and error message are printed in one group, when several threads call it.

### Thread completion

The other piece of synchronization is the thread completion indicator. The thread descriptor contains a method called `wait` which suspend the calling thread until the thread attached to the descriptor has been completed. If the thread is already completed, the method returns immediately.

# simple flag const flag false

# simple tester const ftest (bval) (flag:= bval)

# run the thread and wait const thr (launch (ftest true)) thr:wait assert true flag

This example is taken from the test suites. It checks that a boolean variable is set when started in a thread. Note the use of the `wait` method to make sure the thread has completed before checking for the flag value. It is also worth to note that `wait` is one of the method which guarantees that a thread result is valid.

Another use of the `wait` method can be made with a vector of thread descriptors when one wants to wait until all of them have completed.

# shared vector of threads descriptors const thr-group (Vector)

# wait until all threads in the group are finished const wait-all nil (for (thr) (thr-group) (thr:wait))

### Complete example

We illustrate the previous discussion with a complete example. The idea is to perform a matrix multiplication. A thread is launched when when multiplying one line with one column. The result is stored in the thread descriptor. A vector of thread descriptor is used to store the result.

# initialize the shared library interp:library "afnix-sys"

# shared vector of threads descriptors const thr-group (Vector)

# this procedure waits until all threads in # the group are finished const wait-all nil (for (thr) (thr-group) (thr:wait))

# this procedure initialize a matrix with random numbers # the matrix is a square one with its size as an argument const init-matrix (n) { trans i (Integer 0) const m (Vector) do { trans v (m:append (Vector)) trans j (Integer) do { v:append (afnix:sys:get-random) } (< (j:++) n) } (< (i:++) n) eval m }

# this procedure multiply one line with one column const mult-line-column (u v) { assert (u:length) (v:length) trans result 0 for (x y) (u v) (result:+= (* x y)) eval result }

# this procedure multiply two vectors assuming one # is a line and one is a column coming from the matrix const mult-matrix (mx my) { for (lv) (mx) { assert true (vector-p lv) for (cv) (my) { assert true (vector-p cv) thr-group:append (launch (mult-line-column lv cv)) } } }

# check for some arguments # note the use of errorln method if (== 0 (interp:argv:length)) { errorln "usage: axi 0607.als size" afnix:sys:exit 1 }

# get the integer and multiply const n (Integer (interp:argv:get 0)) mult-matrix (init-matrix n) (init-matrix n)

# wait for all threads to complete wait-all

# make sure we have the right number assert (* n n) (thr-group:length)

### Condition variable

A condition variable is another mechanism to synchronize several threads. A condition variable is modeled with the `Condvar` object. At construction, the condition variable is initialized to `false`. A thread calling the `wait` method will block until the condition becomes `true`. The `mark` method can be used by a thread to change the state of a condition variable and eventually awake some threads which are blocked on it. The following example shows how the main thread blocks until another change the state of the condition.

# create a condition variable const cv (Condvar)

# this function runs in a thread - does some computation # and mark the condition variable const do-something nil { # do some computation .... # mark the condition cv:mark }

# start some computation in a thread launch (do-something)

# block until the condition is changed cv:wait-unlock

# continue here ...

In this example, the condition variable is created at the beginning. The thread is started and the main thread blocks until the thread change the state of the condition variable. It is important to note the use of the `wait-unlock` method. When the main thread is re-started (after the condition variable has been marked), the main thread owns the lock associated with the condition variable. The `wait-unlock` method unlocks that lock when the main thread is restarted. Note also that the `wait-unlock` method reset the condition variable. if the `wait` method was used instead of `wait-unlock` the lock would still be owned by the main thread. Any attempt by other thread to call the mark method would result in the calling thread to block until the lock is released.

The `Condvar` class has several methods which can be used to control the behavior of the condition variable. Most of them are related to lock control. The `reset` method reset the condition variable. The `lock` and `unlock` control the condition variable locking. The `mark`, `wait` and `wait-unlock` method controls the synchronization among several threads.