8.4 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.
with_mutex(+MutexId, :Goal)
Execute Goal while holding MutexId. If Goal leaves choice-points, 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/1 and call_cleanup/3.

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

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, which provides a safer way for handling Prolog-level mutexes. The predicate call_cleanup/[2-3] is another way to guarantee that the mutex is unlocked while retaining non-determinism.

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 the holding thread and Count with the recursive count of the mutex. Otherwise, ThreadId is [] and Count is 0.