Qore Programming Language Reference Manual
1.12.0
|
A thread is an independent sequence of execution of Qore code within a Qore program or script. Each thread has a thread ID or TID.
The first thread of execution in a Qore program has TID 1. TID 0 is always reserved for the special signal handler thread.
The Qore language is designed to be thread-safe and Qore programs should not crash the Qore executable due to threading errors. Threading errors should only cause exceptions to be thrown or application errors to occur.
Threading functionality in Qore is provided by the operating system's POSIX threads library.
New threads are created with the background operator. This operator executes the expression given as an argument in a new thread and returns the TID (integer thread ID) of the new thread to the calling thread. This is most useful for calling user functions or object methods designed to run in a separate thread.
To terminate a thread, the thread_exit statement should be called, as calling the exit() function will terminate the entire process (and therefore all threads) immediately.
All global variables are shared in Qore programs, while local variables (declared with my
) are generally local to each thread (and thus accessed without any mutual-exclusion locking), regardless of location. This means that if a variable is declared with my
at the top level, it will actually have global scope, but also each thread will have its own copy of the variable. In effect, declaring a top-level local variable with my
actually creates a global thread-local variable.
The following code gives an example of declaring a global thread-local variable by using my
at the top-level:
This will print out:
x=2 x=null
Note that the second time the local variable is accessed in the background thread, it has no value.
Due to the way Qore's local variables work, it is illegal to declare a top-level local variable after first block is parsed in the program; that is; if any call to parse() or Qore::Program::parse() is made in an existing program (where a top-level block already exists), and an attempt to declare a new top-level local variable is made, then a ILLEGAL-TOP-LEVEL-LOCAL-VARIABLE
parse exception will be raised.
Access to global variables in qore is wrapped in mutual-exclusion locks to guarantee safe access to global variable data in a multithreaded context. Local variables are thread-local and therefore not locked, except when referenced in a closure or when a reference is taken of them, in which case the local variable's scope is extended to that of the closure's or the reference's, and all accesses to the bound local variable are made within mutual-exclusion locks as these variables may be used in multithreaded contexts.
An alternative to global thread-local variables is offered by the save_thread_data() and get_thread_data() functions (documented in Threading Functions).
synchronized
keyword can be used before function or class method definitions in order to guarantee that the function or method call will only be executed in one thread at a time. As in Java, this keyword can also be used safely with recursive functions and methods (internally a recursive mutual exclusion lock that participates in Qore's deadlock detection framework is used to guarantee thread-exclusivity and allow recursion).synchronized
functions have a dedicated reentrant lock associated with the function, while synchronized
normal class methods share a reentrant lock associated with the object, and a synchronized
static class methods share a reentrant lock associated with the class itself.Class | Description |
AbstractThreadResource | Allows Qore user code to implement thread resources |
AutoGate | A helper class to automatically exit Gate locks when the AutoGate object is deleted |
AutoLock | A helper class to automatically release Mutex locks when the AutoLock object is deleted |
AutoReadLock | A helper class to automatically release read locks when the AutoReadLock object is deleted |
AutoWriteLock | A helper class to automatically release read locks when the AutoWriteLock object is deleted |
Condition | Allows Qore programs to block until a certain condition becomes true |
Counter | A blocking counter class |
Gate | A recursive thread lock |
Mutex | A mutual-exclusion thread lock |
Queue | A thread-safe, blocking queue class (useful for message passing) |
RWLock | A read-write thread lock |
Sequence | A simple, thread-atomic sequence object (increment-only) |
ThreadPool | A flexible, dynamically scalable thread pool |
Function | Description |
delete_all_thread_data() | Deletes the entire thread-local data hash |
delete_thread_data() | Delete the value of a key in the thread-local data hash |
get_all_thread_data() | Retrieves the entire thread-local hash |
get_thread_data() | Retrieves a thread-local value based on a key |
gettid() | Gets the thread's TID (thread identifier) |
mark_thread_resources() | sets a checkpoint for throwing thread resource exceptions |
num_threads() | Returns the number of running threads |
remove_thread_resource() | removes a thread resource from the current thread |
save_thread_data() | Saves a thread-local value against a key |
set_thread_init() | Sets a closure or call reference to run every time a new thread is started in a Program object |
set_thread_resource() | sets a thread resource against the current thread |
thread_list() | Returns a list of TIDs of running threads |
throw_thread_resource_exceptions() | runs thread-resource cleanup routines and throws the associated exceptions |
throw_thread_resource_exceptions_to_mark() | runs thread-resource cleanup routines and throws the associated exceptions to the last mark and clears the mark |
Qore supports a concept called thread resources; thread resources are resources that belong to a particular thread and that require some sort of cleanup of the resource in case the thread is exited without releasing the resource. Examples of thread resources in Qore are as follows:
User classes can implement their own thread resource handling by subclassing AbstractThreadResource (implementing custom cleanup code in the AbstractThreadResource::cleanup() method) and then calling:
If the thread is exited or one of the thread resource cleanup functions is called while the thread resource is set, then the AbstractThreadResource::cleanup() method is called, which should cleanup the thread resource and normally throw a user-friendly exception describing the error, the cleanup actions takenm and how to prevent it in the future.
The thread-resource cleanup functions are:
Qore supports deadlock detection in complex locking scenarios and will throw a THREAD-DEADLOCK
exception rather than allow an operation to be performed that would cause a deadlock. Deadlock detection is implemented for internal locking (global variable and object access), synchronized methods and functions, etc, as well as for all Qore threading classes.
Qore can only detect deadlocks when a lock resource acquired by one thread is required by another who holds a lock that the first thread also needs. Other errors such as forgetting to unlock a global lock and trying to acquire that lock in another thread cannot be differentiated from valid use of threading primitives and will result in a process that never terminates (a deadlocked process). However, common threading errors such as trying to lock the same Mutex twice in the same thread without unlocking it between the two Mutex::lock() calls are caught in Qore and exceptions are thrown. Additionally, locks are tracked as thread resources, so if a thread terminates while holding a lock, an exception will be thrown and the lock will be automatically released.