This chapter forms an integral part of "The Multi-Threading Module" - should this module be implemneted, this chapter along with any chapter constituting part of "The Multi-Threading Module" must be implemented in their entirity.
// - Sharable objects may be used across threads
// - Exclusive objects have more efficient implementations than
// sharable objects, but the behavior is undefined when used
// in multiple threads.
[subr mutex(v)] := {
method __copy__(),
method __final__(),
[method acquire()] := {
method __get__(),
method __set__(),
method __copy__(),
method __final__(),
},
}
The mutex() function creates a mutex which is a sharable object that can be
used across threads. The argument v will be an exclusive object protected by
the mutex.
The the mutex protects its own internal state during __copy__ and __final__,
which makes it a sharable object.
Note: If implemented using reference counting, __copy__ and __final__
methods of the mutex locks the underlying mutex before changing the count, and
unlocks it afterwards.
The acquire() method of a mutex returns a "gift" object that can be used
for accessing v - when the function returns, it is guaranteed that the thread
in which it returns is the only thread holding the value protected by the mutex,
and that until the gift object goes out of scope, there should be no other
thread simultaneously using the value.
Note: The "gift" object is so named, that the exclusive gift is wrapped under a mutex, protected by it before being revealled to the acquiring thread.
The __get__() and the __set__() methods are used to access the object
protected by the mutex. When they're called with the string v as its key
argument, they respectively returns and sets the object protected by the mutex;
on all other values, they returns null. Note that the object loses the
protection of the mutex if it does not go out of scope when the gift
object does.
The __copy__() and __final__() properties increments and decrements
respectively, a conceptual counter - this counter is initially set to 1 by
acquire() and any future functions that may be defined fulfilling similar
role; when it reaches 0, the mutex is 'unlocked', allowing other threads to
acquire the value for use.
Note: A typical implementation of acquire() may lock a mutex, sets the
conceptual counter to 1, creates and returns a value native object. A typical
implementation of the __copy__() method may be as simple as just incrementing
the conceptual counter. A typical implementation of the __final__() method
may decrement the counter, and when it reaches 0, unlocks the mutex.
Note: The conceptual counter is distinct from the reference count of any potential resources used by the value protected by the mutex and the mutex itself.
[subr condvar(mtx)] := {
method __copy__(),
method __final__(),
method wait(),
method broadcast(),
method signal(),
}
The condvar() function creates a condition variable. It monitors a condition
associated with the states protected by the mutex identified by mtx.
Note: Condition variables are created associated with a mutex up front so that potential implementations using reference count can protect that counter with the mutex just like mutex instances. It is strongly advised that implementations use actual atomic reference counts where available if they were to use reference counting for resource management.
The wait() method of a condition variable instance does the following:
mtx specified in the creation argument,all in one single atomic step.
The broadcast() method of a condition variable signals a condition variable
and wakes up all threads that're waiting on it. The signal() method signals
the condition varialbe and wakes up an unspecified subset of threads blocked
on the condition variable - this subset shall not be empty if there are threads
waiting on the condition variable, and this method should typically be more
efficient than broadcast() when there's only 1 waiting thread.
[subr thrd_create(thrd_entry, thrd_param) | subr thrd_self()] := {
method join();
method detach();
method equals(thrd_hnd t2);
}
subr thrd_exit();
The thrd_create() function creates a thread with the thrd_entry as its
entry point, and thrd_param as its first and only argument. thrd_entry
MUST be a subroutine. Its return type is null. On success, a thrd_hnd
thread handle is returned, otherwise, null is returned.
The thrd_self() function returns the thread handle corresponding to the
current thread.
The thrd_exit() function cause the current thread to immediately terminate.
The join() method of a thrd_hnd blocks the calling thread until the thread
referred to by the thread handle termintates. The first such call on a
non-detached thread is supposed to succeed - implementation shall document the
underlying platform API behavior for it; subsequent calls may not necessarily
succeed. The detach() method of a thread handle detaches a thread, after
which, the thread may no longer be joinable, or be detached again. The return
values of these 2 functions are implementation-defined.
The equals() method returns true if the thread handle t2 refers to the
same thread as the thread handle on which the method is called, and false
otherwise.
The thrd_hnd shall be sharable across threads. The existence of a thread
handle does not imply that of the thread.
Note: The thread management facility is bare minimum, so that first it's
directly implementable using existing standard APIs. That second the thread
handle type thrd_hnd carries the least complexity, enabling its share across
threads - although it's not explicitly specified as a sharable type, it shall
behave as such. That third, the usage flexibility makes higher level
constructions such as asynchronously completing subroutines, coroutines,
single-apartment proxy objects, etc. be readily implementable in terms of
the minimal API.
Note: The thread handle type may be implemented as sharable by virtue of it being immutable. A technique of implementing it as sharable is documented at https://langdev.stackexchange.com/a/4633/1388.