pthread_sleepon_timedwait()

Make a thread sleep while waiting

Synopsis:

#include <pthread.h>

int pthread_sleepon_timedwait(
       const volatile void * addr,
       uint64_t nsec );

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.
nsec
A limit on the amount of time to wait, in nanoseconds.

Library:

libc

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

Description:

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

If nsec is nonzero, then pthread_sleepon_timedwait() calls pthread_cond_timedwait(). If the pthread_cond_timedwait() times out, then pthread_sleepon_timedwait() returns ETIMEDOUT. If nsec is zero, then pthread_sleepon_timedwait() calls pthread_cond_wait() instead.

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_timedwait(cachelist, 1000);
}
cachelist->free = ptr->free;

pthread_sleepon_unlock();

To start an operation and wait upon its completion:

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

/* 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_timedwait(&driver->complete, 1000);
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();

The use of a while loop instead of an if handles the case where the wait on addr is woken up using pthread_sleepon_broadcast().

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_timedwait() calls pthread_cond_timedwait(), 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_timedwait(addr, 1000);
        pthread_sleepon_unlock();
    }
    return ret;
}

void _wakeup(void *addr) {
    if(pthread_sleepon_lock() == EOK) {
        pthread_sleepon_broadcast(addr, 1000);
        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:

EDEADLK
The calling thread already owns the controlling mutex.
ETIMEDOUT
The time specified by nsec has passed.
EOK
Success.

Classification:

QNX Neutrino

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