Home Contents Index Summary Previous Next

3.39 Multi-threading (alpha code)

The features described in this section are only enabled on Unix systems providing POSIX threads and if the system is configured using the --enable-mt option. SWI-Prolog multi-theading support is experimental and in some areas not safe.

SWI-Prolog multithreading is based on standard C-language multithreading support. It is not like ParLog or other paralel implementations of the Prolog language. Prolog threads have their own stacks and only share the Prolog heap: predicates, records, flags and other global non-backtrackable data. SWI-Prolog thread support is designed with the following goals in mind.

thread_create(:Goal, -Id, +Options)
Create a new Prolog thread (and underlying C-thread) and start it by executing Goal. If the thread is created succesfully, the thread-identifier of the created thread is unified to Id. Options is a list of options. Currently defined options are:

local(K-Bytes)
Set the limit to which the local stack of this thread may grow. If omited, the limit of the calling thread is used. See also the -L commandline option.

global(K-Bytes)
Set the limit to which the global stack of this thread may grow. If omited, the limit of the calling thread is used. See also the -G commandline option.

trail(K-Bytes)
Set the limit to which the trail stack of this thread may grow. If omited, the limit of the calling thread is used. See also the -T commandline option.

argument(K-Bytes)
Set the limit to which the argument stack of this thread may grow. If omited, the limit of the calling thread is used. See also the -A commandline option.

alias(AliasName)
Associate an `alias-name' with the thread. This named may be used to refer to the thread and remains valid until the thread is joined (see thread_join/2).

detached(Bool)
If false (default), the thread can be waited for using thread_join/2. thread_join/2 must be called on this thread to reclaim the all resources associated to the thread. If true, the system will reclaim all associated resources automatically after the thread finishes. Please not that thread identifiers are freed for reuse after a detached thread finishes or a normal thread has been joined.

The Goal argument is copied to the new Prolog engine. This implies further instantiation of this term in either thread does not have consequences for the other thread: Prolog threads do not share data from their stacks.

thread_self(-Id)
Get the Prolog thread identifier of the running thread. If the thread has an alias, the alias-name is returned.

current_thread(?Id, ?Status)
Enumerates identifiers and status of all currently known threads. Calling current_thread/2 does not influence any thread. See also thread_join/2. For threads that have an alias-name, this name is returned in Id instead of the numerical thread identifier. Status is one of:

running
The thread is running. This is the initial status of a thread. Please note that threats waiting for something are considered running too.

false
The Goal of the thread has been completed and failed.

true
The Goal of the thread has been completed and succeeded.

exited(Term)
The Goal of the thread has been terminated using thread_exit/1 with Term as argument.

exception(Term)
The Goal of the thread has been terminated due to an uncaught exception (see throw/1 and catch/3).

thread_join(+Id, -Status)
Wait for the termination of thread with given Id. Then unify the result-status (see thread_exit/1) of the thread with Status. After this call, Id becomes invalid and all resources associated with the thread are reclaimed. See also current_thread/2.

A thread that has been completed without thread_join/2 being called on it is partly reclaimed: the Prolog stacks are released and the C-thread is destroyed. A small data-structure represening the exit-status of the thread is retained until thread_join/2 is called on the thread.

thread_exit(+Term)
Terminates the thread immediately, leaving exited(Term) as result-state. The Prolog stacks and C-thread are reclaimed.

thread_at_exit(:Goal)
Run Goal after the execution of this thread has terminated. This is to be compared to at_halt/1, but only for the current thread. These hooks are ran regardless of why the execution of the thread has been completed. As these hooks are run, the return-code is already available through current_thread/2.

3.39.1 Thread communication

Prolog threads can exchange data using dynamic predicates, database records, and other globally shared data. In addition, they can send messages to each other. If a threads needs to wait for another thread until that thread has produced some data, using only the database forces the waiting thread to poll the database continuously. Waiting for a message suspends the thread execution until the message has arrived in its message queue.

thread_send_message(+ThreadId, +Term)
Place Term in the message queue of the indicated thread (which can even be the message queue of itself (see thread_self/1). Any term can be placed in a message queue, but note that the term is copied to to receiving thread and variable-bindings are thus lost. This call returns immediately.

thread_get_message(?Term)
Examines the thread message-queue and if necessary blocks execution until a term that unifies to Term arrives in the queue. After a term from the queue has been unified unified to Term, this term is deleted from the queue and this predicate returns.

Please note that not-unifying messages remain in the queue. After the following has been executed, thread 1 has the term b(gnu) in its queue and continues execution using A is gnat.


   <thread 1>
   thread_get_message(a(A)),

   <thread 2>
   thread_send_message(b(gnu)),
   thread_send_message(a(gnat)),

See also thread_peek_message/1.

thread_peek_message(?Term)
Examines the thread message-queue and compares the queued terms with Term until one unifies or the end of the queue has been reached. In the first case the call succeeds (possibly instantiating Term. If no term from the queue unifies this call fails.

thread_signal(+ThreadId, :Goal)
Make thread ThreadId execute Goal at the first opportunity. In the current implementation, this implies at the first pass through the Call-port. The predicate thread_signal/2 itself places Goal into the signalled-thread's signal queue and returns immediately.

Signals (interrupts) do not cooperate well with the world of multi-threading, mainly because the status of mutexes cannot be guaranteed easily. At the call-port, the Prolog virtual machine holds no locks and therefore the asynchronous execution is safe.

Goal can be any valid Prolog goal, including throw/1 to make the receiving thread generate an exception and trace/0 to start tracing the receiving thread.

3.39.2 Thread synchronisation

All internal Prolog operations are thread-safe. This implies two Prolog threads can operate on the same dynamic predicate without corrupting the consistency of the predicate. This section deals with user-level mutexes (called monitors in ADA or critical-sections by Microsoft). A mutex is a MUTual EXclusive device, which implies at most one thread can hold a mutex.

Mutexes are used to realise related updates to the Prolog database. With `related', we refer to the situation where a `transaction' implies two or more changes to the Prolog database. For example, we have a predicate address/2, representing the address of a person and we want to change the address by retracting the old and asserting the new address. Between these two operations the database is invalid: this person has either no address or two addresses (depending on the assert/retract order).

Here is how to realise a correct update:


:- initialization
        mutex_create(addressbook).

change_address(Id, Address) :-
        mutex_lock(addressbook),
        retractall(address(Id, _)),
        asserta(address(Id, Address)),
        mutex_unlock(addressbook).

mutex_create(?MutexId)
Create a mutex. if MutexId is an atom, a named mutex is created. If it is a variable, an anonymous mutex reference is returned. There is no limit to the number of mutexes that can be created.

mutex_destroy(+MutexId)
Destroy a mutex. After this call, MutexId becomes invalid and further references yield an existence_error exception.

mutex_lock(+MutexId)
Lock the mutex. Prolog mutexes are recursive mutexes: they can be locked multiple times by the same thread. Only after unlocking it as many times as it is locked, the mutex becomes available for locking by other threads. If another thread has locked the mutex the calling thread is suspended until to mutex is unlocked.

If MutexId is an atom, and there is no current mutex with that name, the mutex is created automatically using mutex_create/1. This implies named mutexes need not be declared explicitly.

Please note that locking and unlocking mutexes should be paired carefully. Especially make sure to unlock mutexes even if the protected code fails or raises an exception. For most common cases use with_mutex/2, wich provides a safer way for handling prolog-level mutexes.

mutex_trylock(+MutexId)
As mutex_lock/1, but if the mutex is held by another thread, this predicates fails immediately.

mutex_unlock(+MutexId)
Unlock the mutex. This can only be called if the mutex is held by the calling thread. If this is not the case, a permission_error exception is raised.

mutex_unlock_all
Unlock all mutexes held by the current thread. This call is especially useful to handle thread-termination using abort/0 or exceptions. See also thread_signal/2.

current_mutex(?MutexId, ?ThreadId, ?Count)
Enumerates all existing mutexes. If the mutex is held by some thread, ThreadId is unified with the identifier of te holding thread and Count with the recursive count of the mutex. Otherwise, ThreadId is and Count is 0.

with_mutex(+MutexId, :Goal)
Execute Goal while holding MutexId. If Goal leaves choicepointes, these are destroyed (as in once/1). The mutex is unlocked regardless of whether Goal succeeds, fails or raises an exception. An exception thrown by Goal is re-thrown after the mutex has been successfully unlocked. See also mutex_create/2.

Although described in the thread-section, this predicate is also available in the single-threaded version, where it behaves simply as once/1.

3.39.3 Thread-support library(threadutil)

This library defines a couple of useful predicates for demonstrating and debugging multi-threaded applications. This library is certainly not complete.

threads
Lists all current threads and their status. In addition, all `zombie' threads (finished threads that are not detached, nor waited for) are joined to reclaim their resources.

interactor
Create a new console and run the Prolog toplevel in this new console. See also attach_console/0.

attach_console
If the current thread has no console attached yet, attach one and redirect the user streams (input, output, and error) to the new console window. The console is an xterm application. For this to work, you should be running X-windows and your xterm should know the -Sccn.

This predicate has a couple of useful applications. One is to separate (debugging) I/O of different threads. Another is to start debugging a thread that is running in the background. If thread 10 is running, the following sequence starts the tracer on this thread:


?- thread_signal(10, (attach_console, trace)).

3.39.4 Status of the thread implementation

It is assumed that the basic Prolog execution is thread-safe. Various problems are to be expected though, both dead-locks as well as not-thread-safe code in builtin-predicates.