Qore Programming Language Reference Manual 1.19.2
Loading...
Searching...
No Matches
Threading

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.

Creating and Terminating Threads

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.

Threading and Variables

All global variables are shared in Qore programs, while local variables are generally local to each thread (and thus accessed without any mutual-exclusion locking), regardless of location. This means that if a local variable is declared 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 actually creates a global thread-local variable.

The following code gives an example of declaring a global thread-local variable by declaring it at the top-level:

%new-style
%require-types
%strict-args
sub t() {
printf("x=%y\n", x);
}
int x = 2;
t();
background t();

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 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 an 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).

Thread Management and Inter-Thread Communication

synchronized
The 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.
Classes Useful With Threading
The following classes are useful when developing multi-threaded Qore programs:
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
Functions Useful With Threading
The following functions assist writing safe and efficient multi-threaded Qore programs:
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

Thread Resources

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:

  • Mutex: when a lock is acquired, the lock is tracked as a thread resource. If the thread is exited without releasing the lock, the Mutex thread resource cleanup routine is executed which releases the lock and throws a user-friendly exception explaining what happened. When the lock is released, the thread resource is removed.
  • RWLock: read-write locks are treated as thread resources in the same way described for Mutex locks
  • Gate: gate locks are treated as thread resources in the same way described for Mutex locks
  • Datasource: the transaction lock is handled as a thread resource; if a thread exits holding the transaction lock, the Datasource cleanup routine is executed; this will rollback the transaction (freeing the transaction lock) and throw an appropriate exception.
  • DatasourcePool: datasource allocations are handled as a thread resources; if a thread exits without releasing the connection, the DatasourcePool cleanup routine is executed; this will rollback the transaction (releasing the dtasource connection back to the pool) and throw an appropriate exception.

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:

See also
AbstractThreadResource for an example of custom thread resource handling

Deadlocks

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.