Qore Programming Language Reference Manual: Threading

Qore Programming Language Reference Manual 0.8.11

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes

All Classes Namespaces Functions Variables Groups Pages
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 (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:

%require-our
sub t() {
printf("x=%y\n", $x);
}
my 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 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).

# Thread Synchronization 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).

Classes Useful With Threading
  The following classes are useful when developing multi-threaded Qore programs:

  Class | Description
  Mutex | A mutual-exclusion thread lock
  Gate | A recursive thread lock
  RWLock | A read-write thread lock
  Condition | Allows Qore programs to block until a certain condition becomes true
  Counter | A blocking counter class
  Queue | A thread-safe, blocking queue class (useful for message passing)
  Sequence | A simple, thread-atomic sequence object (increment-only)
  ThreadPool | A flexible, dynamically scalable thread pool
  AutoLock | A helper class to automatically release Mutex locks when the AutoLock object is deleted
  AutoGate | A helper class to automatically exit Gate locks when the AutoGate 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

Functions Useful With Threading
  The following functions assist writing safe and efficient multi-threaded Qore programs:

  Function | Description
  save_thread_data() | Saves a thread-local value against a key.
  get_all_thread_data() | Retrieves the entire thread-local hash.
  get_thread_data() | Retrieves a thread-local value based on a key.
  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.
  gettid() | Gets the thread's TID (thread identifier)
  thread_list() | Returns a list of TIDs of running threads
  num_threads() | Returns the number of running threads
  throwThreadResourceExceptions() | runs thread-resource cleanup routines and throws the associated exceptions
  mark_thread_resources() | sets a checkpoint for throwing thread resource 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

# 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.