pthread_sleepon_wait()

Updated: April 19, 2023

Make a thread sleep while waiting

Synopsis:

#include <pthread.h>

int pthread_sleepon_wait( const volatile void * addr );

Arguments:

addr
The handle that you want the thread to wait for. The value of addr is typically a data structure that controls a resource.

Library:

libc

Use the -l c option to qcc to link against this library. This library is usually included automatically.

Description:

The pthread_sleepon_wait() function uses a mutex and a condition variable to sleep on a handle, addr.

The pthread_sleepon* functions provide a simple, uniform way to wait on a variety of resources in a multithreaded application. For example, a multithreaded filesystem may wish to wait on such diverse things as a cache block, a file lock, an operation complete and many others. For example, to wait on a resource:

pthread_sleepon_lock();

while((ptr = cachelist->free) == NULL) {
    pthread_sleepon_wait(cachelist);
}
cachelist->free = ptr->free;

pthread_sleepon_unlock();

To start an operation and wait for its completion:

/* Line up for access to the driver */
pthread_sleepon_lock();
if(driver->busy) {
    pthread_sleepon_wait(&driver->busy);
}

/* We now have exclusive use of the driver */
driver->busy = 1;
driver_start(driver);   /* This should be relatively fast */

/* Wait for something to signal driver complete */
pthread_sleepon_wait(&driver->complete);
pthread_sleepon_unlock();

/* Get the status/data */
driver_complete(driver);

/* Release control of the driver and signal anyone waiting */
pthread_sleepon_lock();
driver->busy = 0;
pthread_sleepon_signal(&driver->busy);
pthread_sleepon_unlock();

pthread_exit(NULL);

Choose carefully when you decide whether to use a while loop:

If you're in doubt, use a while loop, because it guarantees access to the desired resource.

You must call pthread_sleepon_lock(), which acquires the controlling mutex for the condition variable and ensures that another thread won't enter the critical section between the test, block and use of the resource. Since pthread_sleepon_wait() calls pthread_cond_wait(), it releases the controlling mutex when it blocks. It reacquires the mutex before waking up.

The wakeup is accomplished by another thread's calling pthread_sleepon_signal(), which wakes up a single thread, or pthread_sleepon_broadcast(), which wakes up all threads blocked on addr. Threads are woken up in priority order. If there's more than one thread with the same highest priority, the one that has been waiting the longest is woken first.

A single mutex and one condition variable for each unique address that's currently being blocked on are used. The total number of condition variables is therefore equal to the number of unique addrs that have a thread waiting on them. This also means that the maximum number of condition variables never exceeds the number of threads. To accomplish this, condition variables are dynamically created as needed and placed upon an internal freelist for reuse when not.

You might find the pthread_sleepon_*() functions easier to use and understand than condition variables. They also resemble the traditional sleepon() and wakeup() functions found in Unix kernels. They can be implemented as follows:

int _sleepon(void *addr) {
    int ret;

    if((ret = pthread_sleepon_lock()) == EOK) {
        ret = pthread_sleepon_wait(addr);
        pthread_sleepon_unlock();
    }
    return ret;
}

void _wakeup(void *addr) {
    if(pthread_sleepon_lock() == EOK) {
        pthread_sleepon_broadcast(addr);
        pthread_sleepon_unlock();
    }
}

Note that in most Unix kernels, a thread runs until it blocks, and thus need not worry about protecting the condition it checks with a mutex. Likewise, when a Unix wakeup() is called, there isn't an immediate thread switch. Therefore, you can use only the above simple routines (_wakeup() and _sleepon()) if all your threads run with SCHED_FIFO scheduling and at the same priority, thus more closely mimicking Unix kernel scheduling.

Returns:

EOK
Success.
EDEADLK
The calling thread already owns the controlling mutex.

Classification:

QNX Neutrino

Safety:  
Cancellation point Yes
Interrupt handler No
Signal handler Yes
Thread Yes