cwinapipthreadssleepwakeup

Portable way to make a thread sleep for a certain time or until woken up


In my project I spawn a worker thread which deals with an information source. It needs to poll a source (to which it is subscribed) and, whenever needed, change the subscription (i.e. telling the source that the filter criteria for the information it needs have changed).

Polling the source happens at regular intervals, in the minutes range. Changes to the subscription need to be triggered immediately when the main thread identifies the condition for doing so. However, since the operation can be lengthy, it needs to run on the worker thread.

So the worker thread looks like this (in pseudo code—real code is in C):

while(true) {
    acquireWriteLock();
    if (subscriptionChangeNeeded) {
        changeSubscription();
        subscriptionChangeNeeded = false;
    }
    releaseWriteLock();
    pollSource();
    sleep(pollInterval);
}

Now, if the main thread sets subscriptionChangeNeeded right after the worker thread has completed one run of the loop and gone to sleep, the subscription change is going to be delayed by almost the duration of pollInterval.

Therefore, I need a way to wake the thread prematurely from its sleep—or, rather than tell it “sleep for X”, “sleep until I wake you up, but no longer than X”.

I do not need to explicitly tell the thread why it has come out of sleep—it can infer that from subscriptionChangeNeeded. Polling prematurely after changing the subscription is a desirable side effect.

Setting subscriptionChangeNeeded happens at a single location, hence I can easily incorporate a “wake up the worker thread” operation there.

Challenge: the code needs to run on Unix-like OSes as well as Windows. It solves that by implementing a thread abstraction wrapper around the respective APIs.

I am aware both platforms (pthread and Windows) support condition variables, but implementations vary somewhat. Among others, Windows protects the critical section with a critical section object, whereas pthread uses a mutex. Also, older Windows platforms (up to XP/2003) do not support condition variables at all. Finally, condition variables are more powerful than what I need—all I really need is a way to send a thread to sleep and allow it to be woken up by another thread.

What other constructs do the two platforms offer to acomplish that?


Solution

  • After some research, it seems events on WinAPI are closest to what I am trying to accomplish, and they have been around since at least Windows XP (I believe even earlier versions had it). On POSIX, the closest match really are conditional variables, though the mutex is not really necessary.

    Since the project implements its own thread abstraction wrapper, this gives us some extra freedom.

    We’ll introduce a data type event. This is an opaque data structure. On Windows, this corresponds to an event (created as an auto-reset event); on POSIX, this is a struct holding a condition and a mutex.

    When a thread goes to sleep, the POSIX implementation locks the mutex, calls pthread_cond_timedwait and unlocks the mutex as soon as it continues. The Windows implementation just calls WaitForSingleObject.

    To wake up the sleeping thread, the POSIX implementation locks the mutex, calls pthread_cond_signal and unlocks the mutex again. The Windows implementation calls SetEvent.

    On Windows, events can be auto-reset or not. An auto-reset event will be reset as soon as the first waiting thread is woken up (so it will never wake up more than one thread), but will remain set if that thread happens to be running at the time the event is signaled. Other events will remain in signaled state until explicitly reset, thus they can wake up any number of threads or even prevent them from going to sleep in the first place.

    On POSIX, a condition can be signaled (waking up one waiting thread) or broadcast (waking up all waiting threads). As I understand it, it is reset after it fires, even if no thread got woken up.

    Thus, as long as we never have more than one thread waiting on the same event, auto-reset events on Windows behave much like pthread_cond_signal on POSIX. The only difference is that we need to reset the event after handling it. Our thread abstraction wrapper needs to provide a function for that, which calls ResetEvent on Windows and is a no-op on POSIX.

    Unlike conditions, this construct does not provide a mutex to provide synchronized access. Therefore, if the waiting and the signaling thread access a shared data structure, they must implement their own mechanism to synchronize access, e.g. using a mutex or lock.