Luban Multi-threading Tutorial

 

Multi-threading is a built-in part of Luban, you can dispatch and synchronize thread easily. There more than one way to start threads, as below examples show.

 

1.      Explicit Thread Dispatching

You can dispatch expression valuation, assignment and/or a block of code into separate thread in process code by using the & sign, as below examples show.

 

x = myjobs::computeX(arg=100) &

y = myjobs::computeY(arg =200) &

c = a + b &

 

Or you can send any block of code into a separate thread in similar way.

 

{ myjobs::dothis(=); myjob::dothat_after_this(=); myjob::dofinal(=); } &

 

To synchronize, you can use the wait statement that will block until ALL the dispatched threads finish. Take look at the below example.

 

Myjob::doThis(arg=1) &

Myjob::doThatWhiledoTHis( arg=2)&

wait; // stop until doThis and doThat finish

 

You can wait for certain thread to finish by using waitfor statement. And each threads can be identified by its associated variable. You can always associate the thread to the assigned variable when the dispatched is an assignment statement, for example:

 

x = myjobs::computeX(arg=100) &

y = myjobs::computeY(arg =200) &

waitfor x,y;

z = x + y;

 

For dispatched compound statement, the thread is always associated with the variable in the last assignment statement in the block, as below example shows.

 

{ myjobs::dothis(=); myjob::dothat_after_this(=); alldone = true; } &

waitfor alldone;

 

So if you wish to wait for a thread, remember to associate your thread with a variable as above example shows.

 

2.      When Dispatched Threads End

When a Luban script runs to its end or a structure evaluation finishes, it will wait for all its dispatched threads to finish and collect them, like as if there is a wait statement at the end. If use wishes to stop all dispatched threads cancel statement can be used. Cancel statement can not be used to cancel individual thread, it will try to stop ALL threads instead.

 

3.      Implicit Thread in Asynchronous Structure

To understand Asynchronous Structure, we can code a simple of multi-threading producer and consumer case in which producer and consumer use an asynchronous structure for communication.

 

namespace demo;

struct simplequeue

(

input inq;

output outq;

)

as process

{

outq = inq;

}

 

The above component behaves like a simple queue with one end to put in and another end to read out data, plus it will make reader wait when the queue is empty. Then we can use the above component in a sample script like below:

 

q = demo::simplequeue();

{ while ( true ) { obj= myns::producer().produce(); q.inq=obj; } } &

{ while ( true ) myns::consume( obj = q.outq ); } &

 

Above script starts two threads, one to produce object and put it into the inq of the queue named q, while another to read object out of outq of q and feed to myns::consume structure. When the outq is empty, the consumer thread will sleep. The magic is inside object q there is a live thread that moves object from input inq to output outq. So the total number of threads in above example code is three.

 

4.      Start and Synchronization of Asynchronous Structure

The thread inside asynchronous structure can be started implicitly when its input property is set or its output property is got. Or it can be explicitly started by calling start() member function, like below example.

 

q = demo::simplequeue();

q.inq =1 ; // this will start qs internal thread

x = q.outq; // this will start qs internal thread too

q.start(); // this will start qs internal thread too

 

When Luban script ends or Luban structure valuation finishes, all internal variables will be destroyed. If a variable contains a asynchronous structure, it will be destroyed too. The destruction of asynchronous structure will cancel the execution of its internal thread, even if it is still running. To wait for an asynchronous structure to finish its job and become inactive, you can use the waitfinish() member function, like below:

 

q.start();

q.waitfinish(); // wait for q to finish or become inactive.

 

5.      Tangled Threads Surprise

Thread can be tricky sometimes. For example, the below simple code can produce output that surprise you.

 

for( i=1; i<=10; ++i )

{

std::println(obj=i) &

}

 

At the first look, you would think the program print out number 1 to 10 in that order. But the truth is that the program will print out 10 numbers, but which 10 numbers and the order of the numbers are undetermined. The print could vary at each run of the program.

The root of chaos is the thread dispatching part of this code. Every print operation will be dispatched to a different thread. So ten threads will be dispatched. Together the main thread, there will be eleven threads running in this code. The execution order of these eleven threads is not deterministic.

To fix this problem asynchronous structure can be used because its input and output queue can guarantee the order of data passing while the processing still happens inside a separated thread. Below is the new code for above example.

 

printer =demo::asychprinter();

for( i=0; i<10; ++i)

printer.obj=i;

 

And the definition of demo::asynchprinter is below:

 

namespace demo;

asynch struct asynchprinter( input obj;)

as process

{

std::print(obj=input.obj);

}

 

6.      Thread Safety of Luban

Luban engine guarantees the object integrity in the multi-threading environment. If more than one threads operating on the same object, the order of operation is not deterministic, though the integrity of each individual operation is guaranteed. Luban only allows one thread to call member function or operate on a variable at any time.