LME threads are code fragments which are executed concurrently. This section describes threads at the level of the LME virtual machine, scheduled in a single OS-level thread. For multiple OS-level threads, see Parallel execution.
There is always one main thread; additional threads can be created and killed at any time by any thread. Their number is limited only by the available memory. A new thread is created with the function to be executed (as a function reference, a function name, or an inline function) and optional arguments.
Threads communicate together by exchanging data in global variables or by calling functions with persistent variables. Semaphores can be created to avoid reading a variable while it is being modified by another thread, or for solving other synchronization problems. Thread switching occurs between elementary operations (such as the execution of a function or an operator, or the branch implied by a conditional or iteration command). For example, the simple expression x(end) where x is a global variable (which gets the last element of the vector x) may not give the expected result if another thread changes the size of x between the evaluation of end and the retrieval of the vector element. In that case, a semaphore should be locked around x(end) and around the modification of x.
Delete a semaphore.
semaphoredelete(id) deletes a semaphore which was created with semaphorenew, ignoring its locked or unlocked state. It is an error to use its id afterwards.
Lock a semaphore.
semaphorelock(id) b = semaphorelock(id)
semaphorelock(id) locks the semaphore specified by id, so that it cannot be locked again before being unlocked with semaphoreunlock. Without output argument, semaphorelock waits until another thread unlocks it. With an output argument, it locks it and returns true if the semaphore is not already locked, and it returns immediately false otherwise as an indication of failure.
Create a new semaphore.
id = semaphorenew
A semaphore is a mechanism which gives to a thread the exclusive access to a resource. To request the access, the semaphore is locked. Once it is locked, no other thread can lock it until it is unlocked. An attempt to lock a semaphore while it is already locked will either wait until the semaphore is unlocked, or fail immediately.
Semaphores are typically used when data shared by two or more threads are modified in several steps (these data can be stored in a global variable or in a file).
semaphorenew creates a new semaphore and returns the identifier which should be used with all other semaphore-related functions.
The code below creates two threads which both use a global variable counter. The first thread continuously increments it, while the second thread resets it to 0 every second.
threadId1 = threadnew(@t1); threadId2 = threadnew(@t2);
Functions t1 and t2 are defined as
function t1 global counter; while true counter = counter + 1; end function t2 global counter; while true threadsleep(1); counter = 0; end
The problem with the code above is that the execution of the first thread can be interrupted right after counter+1 has been evaluated (with a result of 3742197, for instance), but before the result has been assigned to counter. If the second thread resets counter at that time, the first thread will immediately undo that by assigning 3742197 to counter. To avoid that, a semaphore should be used to delay resetting counter to after the new value is assigned in the first thread:
function t1 global counter; while true semaphorelock(countersem); counter = counter + 1; semaphoreunlock(countersem); end function t2 global counter; while true threadsleep(1); semaphorelock(countersem); counter = 0; semaphoreunlock(countersem); end
The semaphore is created before the threads and its identifier stored in global variable countersem:
global countersem; countersem = semaphorenew; threadId1 = threadnew(@t1); threadId2 = threadnew(@t2);
Unlock a semaphore.
semaphoreunlock(id) unlocks the semaphore specified by id, so that it can be locked again by any thread. If a thread was blocked by executing semaphorelock on the semaphore which is unlocked, it will lock the thread and resume execution. If several threads are waiting on the same semaphore, only one of them resumes execution. There is no queue for waiting threads; which one resumes execution is unspecified.
Kill a thread.
threadkill(id) interrupts execution of the thread specified by its id and discards the data which had been are allocated for it by threadnew.
Create a new thread.
id = threadnew(fun) id = threadnew(fun, opt) id = threadnew(fun, opt, par1, par2, ...)
threadnew(fun) creates a new thread which will execute function fun without argument in parallel with the current thread and all other running threads. Function fun can be an inline function, a reference to function, or the name of a function. threadnew returns a thread id which is used by threadkill. The thread terminates when the function does (at the end of the function body or by executing return) or if is interrupted with threadkill. The output arguments of the function cannot be retrieved. If the thread produces a result, it should transmit it via another mean, such as global variables.
threadnew(fun,opt) specifies options created with threadset. Additional arguments, if any, are provided to function fun as its own arguments.
Options for thread creation.
options = threadset options = threadset(name1, value1, ...) options = threadset(options0, name1, value1, ...)
threadset(name1,value1,...) creates the option argument used by threadnew. Options are specified with name/value pairs, where the name is a string which must match exactly the names in the table below. Case is significant. Options which are not specified have a default value. The result is a structure whose fields correspond to each option. Without any input argument, threadset creates a structure with all the default options. Note that threadnew also interprets the lack of an option argument, or the empty array , as a request to use the default values.
When its first input argument is a structure, threadset adds or changes fields which correspond to the name/value pairs which follow.
Here is the list of permissible options:
|StackSize||8192||size allocated for the execution stack|
|Priority||0||thread priority, from -20 (lowest) to 20 (highest)|
|Running||true||true for a running thread, false if suspended|
|Sandbox||false||true for execution in a sandbox|
A thread with Sandbox set to true runs with the same restrictions as code executed with sandbox.
Wait for a specified amount of time.
threadsleep(time) threadsleep(time, true)
threadsleep(time) waits at least the specified amount of time (specified in seconds with a resolution which depends on the platform), permitting other threads to run. threadsleep(time,false) has the same effect.
threadsleep(time,true) waits for the specified amount of time relatively with the end of the previous threadsleep of the current thread. By having threadsleep(time,true) in a loop, a fixed execution frequency can be achieved even if the processing time required for the other statements in the loop can change (provided that the amount of time specified for threadsleep is at least as large as the processing time).