CondVar:
Filter:
Classes | Scheduling

CondVar : Object

Condition variable: block one or more threads until some condition is true
Source: CondVar.sc

CondVar is a condition variable, a low-level synchronization tool which allows one or more threads to wait until some condition becomes true. Although it has "variable" in the name, a condition variable doesn't wrap a variable like, for example, FlowVar does. The condition referred to here is totally external to the CondVar object, and can be any arbitrary state in your code. CondVar supports two fundamental operations: waiting and signalling. Waiting means suspending execution of the current thread until the relevant condition becomes true at some point in the future. Signalling means indicating to other threads that the condition is now true, and that they should continue executing.

When waiting on a CondVar, you may optionally pass a predicate (an object, usually a Function, that returns a Boolean when evaluated with value) as an argument. This leads to two general ways of using CondVar:

The following code illustrates these two approaches:

Internally, CondVar is little more than a queue of threads which are waiting for the condition to become true. When signaling, you have the choice of waking only the next waiting thread on the queue, if one exists (signalOne), or waking all waiting threads in the queue (signalAll).

Note that threads in the queue do not resume immediately upon a signal. They are scheduled to resume immediately after the signalling thread relinquishes control, either by yield or wait, or by reaching the end of a playing Routine. In the examples above, the signalOne Routine simply ends, so there is no need to yield anything.

In other languages, you may see that condition variables also use a mutex for synchronization. This is typically necessary to protect shared access to the condition state. However, since SuperCollider's interpreter is single-threaded, each running thread implicitly holds the global interpreter mutex and more fine-grained mutexes are unnecessary.

Class Methods

CondVar.new

Create a new instance.

Inherited class methods

Instance Methods

.wait(predicate)

The behavior depends on whether a predicate is given. If no predicate is given, this method simply blocks the current thread until it is woken by signalOne or signalAll. Otherwise, this method is equivalent to while { predicate.value.not } { cond.wait }. In other words, the thread only blocks if the predicate is false, otherwise it blocks and only resumes once the thread has been signalled and the predicate is true.

This method must only be called within a Routine or Routine wrapper (for example, Taskor Tdef).

Arguments:

predicate

A condition to be checked before blocking, and before resuming after being woken by signalOne or signalAll. If predicate.value.not is true, execution resumes. Typically, this is a Function that returns a Boolean.

predicate is always executed on the thread which called wait. If evaluating the predicate throws an exception, the exception will propagate up on the thread which called wait, and the thread will no longer be waiting on this CondVar.

When wait returns, the predicate was true. It may not be true later, for instance if it depends on external factors such as the system time or the state of the server.

Returns:

this object

.waitFor(timeoutBeats, predicate)

Similar to wait, but the thread will also be unblocked if the relative timeout timeoutBeats expires.

If predicate is nil, this method blocks the current thread until it is woken by signalOne or signalAll, or the timeout expires. If predicate is not nil, this method returns immediately if predicate.value is true, and otherwise blocks either until the timeout expires, or until the thread is woken by signalOne or signalAll and predicate.value is true.

Because the interpreter's thread scheduler is not preemptive, an expiring timeout will only wake the thread if other threads are idle. You are not guaranteed that the thread will be woken close to the timeout duration expiring, or even that it will be woken at all. This could happen, for example, if some other thread enters an infinite loop and never yields.

This method must only be called within a Routine or Routine wrapper (for example, Task or Tdef).

Arguments:

timeoutBeats

A duration in beats to wait before timing out. This value is converted according to the following rules: if its class is Integer or Float, it remainds unchanged; otherwise, if it responds to asInteger, this method is called; otherwise, if it responds to asFloat, this method is called. After this, if the resulting value is not an Integer or a Float, an error is thrown. An error is also thrown if the resulting value is inf or not a number (NaN). If the resulting value is less than or equal to 0, waitFor returns the result of predicate.valueimmediately if predicate is not nil and false otherwise.

These strict checks are made to protect the thread which handles timeouts.

predicate

A condition to be checked before blocking, and before resuming after being woken by signalOne or signalAll or a timeout. Typically, this is a Function that returns a Boolean.

predicate is always executed on the thread which called wait. If evaluating the predicate throws an exception, the exception will propagate up on the thread which called wait, and the thread will no longer be waiting on this CondVar.

Returns:

If the thread was woken because the timeout expired, then returns predicate.value if one was given and false if predicate was nil. Otherwise, returns true.

In other words, if predicate is non-nil, a return value of true means that predicate.value was true when the thread resumed.

.signalOne

Wakes one thread waiting on this Condition. Threads are woken in the order in which they called wait or waitFor. If a thread is woken and was waiting with a predicate, and that predicate is still false, it will wait again and be placed at the end of the queue of threads waiting on this CondVar. Another thread will not be woken in that case.

Returns:

this object

.signalAll

Wakes all threads waiting on this Condition. Threads are woken in the order in which they called wait or waitFor. If threads were waiting with predicates and their predicates are still false after being woken, they will block again in the same order as before this method was called.

Returns:

this object

.shallowCopy

.copy

From superclass: Object

.deepCopy

Throws a ShouldNotImplementError; CondVars cannot be copied, shallow copied, or deep copied.

Inherited instance methods

Examples

Simple interactive example

Producer-consumer queue: single producer, single consumer

The following example shows a CondVar used to manage a simple producer-consumer queue with one consumer and one producer.

The producer thread adds "tasks" to the queue, while the consumer thread removes and "processes" them. When there is no work to do, the consumer has to wait until work is available. When there is too much work to do, the producer thread should avoid creating more tasks. Try playing with the wait times for each thread to see what happens.

Producer-consumer queue: multiple producers, multiple consumers

Here is another producer-consumer example with multiple producers and multiple consumers. Note how easily the code above can be generalized.

Using timeouts

Sometimes, you want to wait on an external condition that may never come true, or you want to do something else in case you've been waiting for a long time. This is a good place to use waitFor. In our example, we're using a Routine that sometimes fails to set the desired condition to simulate this "unreliable" task. Some practical examples of this might be a long asynchronous task on the server or in a call to unixCmd that doesn't finish in time, or doesn't produce the result we were looking for.

We can also rewrite this example so that we get signalled no matter what, but the condition might still not come true.