threads
The thread module provides functionality for creating and controlling threads. With the thread module, you can create applications that perform multiple operations at the same time.
Operations that can potentially block/hold up other operations can be executed on separate threads.
As the workload of your application increases, the need to offload long tasks to a separate thread of execution becomes apparent when creating scalable applications.
Create and start a new thread
You can create a thread by creating a new instance if the Thread
class wither directly or via the thread()
function and passing
in a function delegate that will be called when the thread is run.
To start the thread, you can call the start function passing
parameters for the thread delegate function to it directly to the
start function.
For example, the following code creates and start a new thread by creating an instance of the Thread class directly,
import thread
var th = thread.Thread(@(t, name) {
echo name * 5
})
th.start('John')
The thread()
function serves as a syntax sugar for this as well
as a module function and like other blade module functions is the
conventional way to create an instance of a thread. The example
below is a rewrite of the previous example with the module function.
import thread
var th = thread(@(t, name) {
echo name * 5
})
th.start('John')
Since for most use-cases this is exactly the process you'll want and
not very often will you need to configure settings on a thread, the
start()
module function provides a simply way of combining this
process into a single function call. However, unlike with the Thread
instance or thread function, the start function accepts it's delegate
function arguments as a list in its second argument. This makes it
very friendly for creating fine-tuned and non-predefined arguments
to the delegate function.
The example below rewrites the previous functionality by using the start function.
import thread
var th = thread.start(@(t, name) {
echo name * 5
}, ['John'])
Notice that the start()
function is more concise. Unless you need to
configure the thread behavior before starting a thread, the start()
function is the idiomatic way to create threads.
NOTICE THE t VARIABLE?
When a thread's delegate function accepts parameters, it will always be given the thread instance itself as the first argument. Any other argument passed into the function will be received in the succeeding parameters.
Awaiting a thread
You can use the Thread.await() thread method to wait to a thread to finish executing its task before continuing execution on the calling thread. This will block the current thread until the awaited thread has exited.
import thread
import os
var th = thread.start(@{
os.sleep(5)
})
th.await()
The example above will wait for the thread which will block for 5 seconds to exit.
Stopping a thread
A thread can be stopped from the calling threa (the thread that created it) as well as from within the thread execution function itself using the Thread.cancel() thread method.
The example below shows how to cancel a thread from the calling thread.
import thread
import os
var th = thread.start(@{
os.sleep(5)
})
echo "I'm cancelling on you!"
th.cancel()
You'll notice that the thread was immediately cancelled immediately after
printing the text I'm cancelling on you!
which in itself was printed
immediately after the thread started. For this reason, we never got to see
the thread wait for the specified 5 seconds.
We can also cancel a thread from within its execution function. The example
below shows how to achieve this with the thread.cancel()
method.
import thread
import os
var th = thread.start(@(t) {
os.sleep(3)
echo "I'm cancelling on you!"
t.cancel()
# we're never reaching here...
os.sleep(15)
})
th.await()
If you run the above example, you'll notice that the program stopped immediately
after printing the text I'm cancelling on you!
as well. It never slept for the
specified 15 seconds; this is because the thread has been stopped.
Fields
- thread.cpu_count ➝ number
- The number of CPU cores available on the current device.
Functions
- thread.thread(delegate, stack_size)
-
Returns a new instance of Thread.
@see Constructor
- @params:
- function delegate
- number? stack_size
- @returns: Thread
- @params:
- thread.start(delegate, args)
-
Creates a new thread and automatically starts the thread using the default options and arguments.
- @params:
- function delegate
- list args
- @params:
Classes
- class Thread
-
The thread class exposes methods to manage creating, running, and controlling threads.
@class
- .Thread(delegate, stack_size) ➝ Constructor
-
The delegate function passed to the constructor may accept zero or more parameters. When it accepts no parameter, the function will be called without any argument when run otherwise, it will be called with as many argument as it can receive.
When a delegate accepts arguments, the first argument passed will always be the thread object itself followed by the arguments it received from start.
For example, in the following thread execution, the first parameter t in the delegate will receive the thread object itself.
var th = Thread(@(t) { echo t.get_id() }) th.start(21)
The delegate function doesn't raise an exception because parameter t never received the
start()
argument but the thread itself. In the next example, the function accepts the start argument. Note that the start argument was received starting from the second argument.var th = Thread(@(t, balance) { echo balance }) th.start(21)
The optional second parameter allows us to set the size of the stack used for the thread when started.
- @params:
- function delegate
- number? stack_size
- @params:
- .start(...)
-
Starts the thread by putting it in the running state and passing any argument to the function to the thread function itself.
If no argument is provided, no argument is passed to the function otherwise, arguments will be passed to the thread function only when the thread function is only able to accept them.
NOTE:* A thread can only be started once.
- @params:
- any... args
- @params:
- .start_from_list(args)
-
Same as
start()
but takes the argument from a list instead.- @params:
- list args
- @params:
- .cancel()
-
Terminates the thread by sending kill signals to the thread and freeing all associated resources afterwards. If the thread has already been cancelled, then it does nothing.
Returns
true
if the thread was successfully terminated and all resources freed otherwise it returnsfalse
.- @returns: bool
- .detach()
-
Marks this thread as a detached thread.
When a detached thread terminates, its resources are automatically released back to the system without the need for another thread to join with the terminated thread.
Once a thread is detached, it can't be awaited anymore.
The detached attribute merely determines the behavior of the system when the thread terminates; it does not prevent the thread from being terminated if the process terminates (or equivalently, if the main thread returns).
Either
await() or
detach()` should be called for each thread that an application creates, so that system resources for the thread can be released. (But note that the resources of any threads for which one of these actions has not been done will be freed when the process terminates.).If the thread was already detached, it simply returns
true
and does nothing.- @returns: bool
- .await()
-
Suspends execution until the thread terminates. If the thread has already terminated, it returns immediately.
Multiple threads can call
await()
at the same time, but only one of them will be actively waiting while others will remain in a suspended state until the thread exits. Ifawait()
is called from another cancelled thread, then this thread will remain awaitable.Failure to join with a thread that is joinable (i.e., one that is not detached), produces a "zombie thread". Avoid doing this, since each zombie thread consumes some system resources, and when enough zombie threads have accumulated, it will no longer be possible to create new threads (or processes).
All of the threads in an application are peers: any thread can join with any other thread in the process.
- .sleep(duration)
-
Causes the current thread to sleep for the specified number of seconds.
@notes:
- This method can only be called from the thread function.
- @params:
- number duration
- .yield()
-
Causes the calling thread to relinquish the CPU.
The thread is moved to the end of the queue for its static priority and a new thread gets to run.
If the calling thread is the only thread in the highest priority list at that time, it will continue to run after a call to
yield()
.On success, returns
true
and otherwisefalse
.@notes:
- This method can only be called from the thread function.
- @returns: bool
- .set_name(name)
-
Sets the internal name for the calling thread to string value specified by name argument.
By default, all the threads inherit the program name. The
set_name()
function can be used to set a unique name for a thread, which can be useful for debugging multithreaded applications. The thread name should be a meaningful string, whose length is restricted to 15 characters.Returns
true
if successful andfalse
otherwise.@notes:
- This method can only be called from the thread function.
- @params:
- string name
- @returns: bool
- .get_name()
-
Returns the name of the current thread.
@notes:
- This method can only be called from the thread function.
- @returns: string
- .set_stack_size(size)
-
sets the stack size attribute of the thread attributes object to the value specified in size.
The stack size attribute determines the minimum size (in bytes) that will be allocated for the thread when created.
Some systems have a minimum stack size and setting the number below that can lead to undefined behaviors. For this reason, the minimum stack size allowed is
16384
bytes (16kb) and the default stack size when not set is 65536 bytes (64kb).On some systems, this setting will be ignored if size is not a multiple of the system page size.
- @params:
- number size
- @params:
- .get_stack_size()
-
Returns the size of the stack allocated to the thread when created.
- @returns: number
- .get_id()
-
Returns the ID of the current thread.
- @returns: number
- .is_alive()
-
Returns true if the thread is started and alive (running) or false if not.
- @returns: bool