Date: Sat, 25 Dec 2004 13:12:25 GMT From: David Xu <davidxu@FreeBSD.org> To: Perforce Change Reviews <perforce@freebsd.org> Subject: PERFORCE change 67676 for review Message-ID: <200412251312.iBPDCP35037106@repoman.freebsd.org>
next in thread | raw e-mail | index | archive | help
http://perforce.freebsd.org/chv.cgi?CH=67676 Change 67676 by davidxu@davidxu_tiger on 2004/12/25 13:11:56 Use new umtx's condition variable feature to implement pthread condition variable, we only need a few of lines to implement it. The condition variable has already ablitity to be shared among processes, but because current pthread_cond_t is defined as a pointer, it can not be shared until pthread.h is changed, that needs version bump. Cancellation point does not work, will be added later. Affected files ... .. //depot/projects/davidxu_thread/src/lib/libthread/thread/thr_cond.c#4 edit Differences ... ==== //depot/projects/davidxu_thread/src/lib/libthread/thread/thr_cond.c#4 (text+ko) ==== @@ -35,22 +35,20 @@ #include <errno.h> #include <string.h> #include <pthread.h> +#include <sys/limits.h> + #include "thr_private.h" -#define THR_IN_CONDQ(thr) (((thr)->sflags & THR_FLAGS_IN_SYNCQ) != 0) -#define THR_CONDQ_SET(thr) (thr)->sflags |= THR_FLAGS_IN_SYNCQ -#define THR_CONDQ_CLEAR(thr) (thr)->sflags &= ~THR_FLAGS_IN_SYNCQ - /* * Prototypes */ -static inline void cond_queue_remove(pthread_cond_t, pthread_t); -static inline void cond_queue_enq(pthread_cond_t, pthread_t); -static void cond_wait_backout(void *); -static inline void check_continuation(struct pthread *, - struct pthread_cond *, pthread_mutex_t *); -static int init_static(struct pthread *thread, - pthread_cond_t *cond); +static inline void check_continuation(struct pthread *, + struct pthread_cond *, pthread_mutex_t *); +static int init_static(struct pthread *thread, + pthread_cond_t *cond); +static int cond_wait_common(pthread_cond_t *cond, pthread_mutex_t *mutex, + const struct timespec *abstime); +static int cond_signal_common(pthread_cond_t *cond, int broadcast); /* * Double underscore versions are cancellation points. Single underscore @@ -69,61 +67,24 @@ int _pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr) { - enum pthread_cond_type type; pthread_cond_t pcond; - int flags; int rval = 0; if (cond == NULL) rval = EINVAL; else { - /* - * Check if a pointer to a condition variable attribute - * structure was passed by the caller: - */ - if (cond_attr != NULL && *cond_attr != NULL) { - /* Default to a fast condition variable: */ - type = (*cond_attr)->c_type; - flags = (*cond_attr)->c_flags; + if ((pcond = (pthread_cond_t) + malloc(sizeof(struct pthread_cond))) == NULL) { + rval = ENOMEM; } else { - /* Default to a fast condition variable: */ - type = COND_TYPE_FAST; - flags = 0; - } - - /* Process according to condition variable type: */ - switch (type) { - /* Fast condition variable: */ - case COND_TYPE_FAST: - /* Nothing to do here. */ - break; - - /* Trap invalid condition variable types: */ - default: - /* Return an invalid argument error: */ - rval = EINVAL; - break; + /* + * Initialise the condition variable structure: + */ + umtx_init(&pcond->c_lock); + pcond->c_count = 0; + pcond->c_flags = 0; + *cond = pcond; } - - /* Check for no errors: */ - if (rval == 0) { - if ((pcond = (pthread_cond_t) - malloc(sizeof(struct pthread_cond))) == NULL) { - rval = ENOMEM; - } else { - /* - * Initialise the condition variable - * structure: - */ - _UMTX_INIT(&pcond->c_lock); - TAILQ_INIT(&pcond->c_queue); - pcond->c_flags = COND_FLAGS_INITED; - pcond->c_type = type; - pcond->c_mutex = NULL; - pcond->c_seqno = 0; - *cond = pcond; - } - } } /* Return the completion status: */ return (rval); @@ -155,8 +116,15 @@ rval = EINVAL; else { /* Lock the condition variable structure: */ - THR_LOCK_ACQUIRE(curthread, &(*cond)->c_lock); - + rval = UMTX_LOCK(&(*cond)->c_lock, curthread->tid); + if (rval) + return (rval); + while ((*cond)->c_count) { + rval = umtx_wake(&(*cond)->c_count, (*cond)->c_count); + if (rval <= 0) + break; + (*cond)->c_count -= rval; + } /* * NULL the caller's pointer now that the condition * variable has been destroyed: @@ -165,7 +133,7 @@ *cond = NULL; /* Unlock the condition variable structure: */ - THR_LOCK_RELEASE(curthread, &cv->c_lock); + umtx_unlock(&cv->c_lock, curthread->tid); /* Free the cond lock structure: */ @@ -180,186 +148,56 @@ return (rval); } -int -_pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) +static int +cond_wait_common(pthread_cond_t *cond, pthread_mutex_t *mutex, + const struct timespec *abstime) { struct pthread *curthread = _get_curthread(); int rval = 0; - int done = 0; - int mutex_locked = 1; - int seqno; - - if (cond == NULL) - return (EINVAL); /* * If the condition variable is statically initialized, * perform the dynamic initialization: */ - if (*cond == NULL && - (rval = init_static(curthread, cond)) != 0) + if (__predict_false(*cond == NULL && + (rval = init_static(curthread, cond)) != 0)) return (rval); - /* - * Enter a loop waiting for a condition signal or broadcast - * to wake up this thread. A loop is needed in case the waiting - * thread is interrupted by a signal to execute a signal handler. - * It is not (currently) possible to remain in the waiting queue - * while running a handler. Instead, the thread is interrupted - * and backed out of the waiting queue prior to executing the - * signal handler. - */ - - /* Lock the condition variable structure: */ - THR_LOCK_ACQUIRE(curthread, &(*cond)->c_lock); - seqno = (*cond)->c_seqno; - do { - /* - * If the condvar was statically allocated, properly - * initialize the tail queue. - */ - if (((*cond)->c_flags & COND_FLAGS_INITED) == 0) { - TAILQ_INIT(&(*cond)->c_queue); - (*cond)->c_flags |= COND_FLAGS_INITED; + if ((rval = UMTX_LOCK(&(*cond)->c_lock, curthread->tid)) == 0) { + rval = _mutex_cv_unlock(mutex); + if (__predict_false(rval)) { + umtx_unlock(&(*cond)->c_lock, curthread->tid); + return (rval); } - /* Process according to condition variable type: */ - switch ((*cond)->c_type) { - /* Fast condition variable: */ - case COND_TYPE_FAST: - if ((mutex == NULL) || (((*cond)->c_mutex != NULL) && - ((*cond)->c_mutex != *mutex))) { - /* Return invalid argument error: */ - rval = EINVAL; - } else { - /* Reset the timeout and interrupted flags: */ - curthread->timeout = 0; - curthread->interrupted = 0; + /* I don't think you may have INIT_MAX threads. */ + if ((*cond)->c_count != INT_MAX) + (*cond)->c_count++; - /* - * Queue the running thread for the condition - * variable: - */ - cond_queue_enq(*cond, curthread); + rval = umtx_timedwait(&(*cond)->c_lock, curthread->tid, + &(*cond)->c_count, abstime); + if (rval == EINTR) + rval = 0; + else if (rval == EAGAIN) /* POSIX needs ETIMEDOUT */ + rval = ETIMEDOUT; - /* Remember the mutex: */ - (*cond)->c_mutex = *mutex; - curthread->sigbackout = cond_wait_backout; - - /* Wait forever: */ - curthread->wakeup_time.tv_sec = -1; - - /* Unlock the mutex: */ - if (mutex_locked && - ((rval = _mutex_cv_unlock(mutex)) != 0)) { - /* - * Cannot unlock the mutex, so remove - * the running thread from the condition - * variable queue: - */ - cond_queue_remove(*cond, curthread); - curthread->sigbackout = NULL; - - /* Check for no more waiters: */ - if (TAILQ_FIRST(&(*cond)->c_queue) == NULL) - (*cond)->c_mutex = NULL; - } - else { - /* - * Don't unlock the mutex the next - * time through the loop (if the - * thread has to be requeued after - * handling a signal). - */ - mutex_locked = 0; - - /* - * This thread is active and is in a - * critical region (holding the cv - * lock); we should be able to safely - * set the state. - */ - THR_LOCK_SWITCH(curthread); - THR_SET_STATE(curthread, PS_COND_WAIT); - - /* Remember the CV: */ - curthread->data.cond = *cond; - - /* Unlock the CV structure: */ - THR_LOCK_RELEASE(curthread, - &(*cond)->c_lock); - - /* Schedule the next thread: */ - _thr_sched_switch_unlocked(curthread); - - /* - * XXX - This really isn't a good check - * since there can be more than one - * thread waiting on the CV. Signals - * sent to threads waiting on mutexes - * or CVs should really be deferred - * until the threads are no longer - * waiting, but POSIX says that signals - * should be sent "as soon as possible". - */ - done = (seqno != (*cond)->c_seqno); - if (done && !THR_IN_CONDQ(curthread)) { - /* - * The thread is dequeued, so - * it is safe to clear this. - */ - curthread->data.cond = NULL; - curthread->sigbackout = NULL; - check_continuation(curthread, - NULL, mutex); - return (_mutex_cv_lock(mutex)); - } - - /* Relock the CV structure: */ - THR_LOCK_ACQUIRE(curthread, - &(*cond)->c_lock); - - /* - * Clear these after taking the lock to - * prevent a race condition where a - * signal can arrive before dequeueing - * the thread. - */ - curthread->data.cond = NULL; - curthread->sigbackout = NULL; - done = (seqno != (*cond)->c_seqno); - - if (THR_IN_CONDQ(curthread)) { - cond_queue_remove(*cond, - curthread); - - /* Check for no more waiters: */ - if (TAILQ_FIRST(&(*cond)->c_queue) == NULL) - (*cond)->c_mutex = NULL; - } - } - } - break; - - /* Trap invalid condition variable types: */ - default: - /* Return an invalid argument error: */ - rval = EINVAL; - break; - } - - check_continuation(curthread, *cond, - mutex_locked ? NULL : mutex); - } while ((done == 0) && (rval == 0)); - - /* Unlock the condition variable structure: */ - THR_LOCK_RELEASE(curthread, &(*cond)->c_lock); - - if (mutex_locked == 0) + /* + * Note! we don't touch condition variable after resuming! + * this makes it possible that waker can destroy the condition + * variable after calling pthread_cond_broadcast(), please + * see Single UNIX Specification Version 3 of + * pthread_cond_destroy(). + */ + check_continuation(curthread, NULL, mutex); _mutex_cv_lock(mutex); + } + return (rval); +} - /* Return the completion status: */ - return (rval); +int +_pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + return cond_wait_common(cond, mutex, NULL); } __strong_reference(_pthread_cond_wait, _thr_cond_wait); @@ -371,7 +209,7 @@ int ret; _thr_cancel_enter(curthread); - ret = _pthread_cond_wait(cond, mutex); + ret = cond_wait_common(cond, mutex, NULL); _thr_cancel_leave(curthread, 1); return (ret); } @@ -380,192 +218,9 @@ _pthread_cond_timedwait(pthread_cond_t * cond, pthread_mutex_t * mutex, const struct timespec * abstime) { - struct pthread *curthread = _get_curthread(); - int rval = 0; - int done = 0; - int mutex_locked = 1; - int seqno; - - THR_ASSERT(curthread->locklevel == 0, - "cv_timedwait: locklevel is not zero!"); - - if (abstime == NULL || abstime->tv_sec < 0 || abstime->tv_nsec < 0 || - abstime->tv_nsec >= 1000000000) + if (abstime == NULL) return (EINVAL); - /* - * If the condition variable is statically initialized, perform dynamic - * initialization. - */ - if (*cond == NULL && (rval = init_static(curthread, cond)) != 0) - return (rval); - - /* - * Enter a loop waiting for a condition signal or broadcast - * to wake up this thread. A loop is needed in case the waiting - * thread is interrupted by a signal to execute a signal handler. - * It is not (currently) possible to remain in the waiting queue - * while running a handler. Instead, the thread is interrupted - * and backed out of the waiting queue prior to executing the - * signal handler. - */ - - /* Lock the condition variable structure: */ - THR_LOCK_ACQUIRE(curthread, &(*cond)->c_lock); - seqno = (*cond)->c_seqno; - do { - /* - * If the condvar was statically allocated, properly - * initialize the tail queue. - */ - if (((*cond)->c_flags & COND_FLAGS_INITED) == 0) { - TAILQ_INIT(&(*cond)->c_queue); - (*cond)->c_flags |= COND_FLAGS_INITED; - } - - /* Process according to condition variable type: */ - switch ((*cond)->c_type) { - /* Fast condition variable: */ - case COND_TYPE_FAST: - if ((mutex == NULL) || (((*cond)->c_mutex != NULL) && - ((*cond)->c_mutex != *mutex))) { - /* Return invalid argument error: */ - rval = EINVAL; - } else { - /* Set the wakeup time: */ - curthread->wakeup_time.tv_sec = abstime->tv_sec; - curthread->wakeup_time.tv_nsec = - abstime->tv_nsec; - - /* Reset the timeout and interrupted flags: */ - curthread->timeout = 0; - curthread->interrupted = 0; - - /* - * Queue the running thread for the condition - * variable: - */ - cond_queue_enq(*cond, curthread); - - /* Remember the mutex and sequence number: */ - (*cond)->c_mutex = *mutex; - curthread->sigbackout = cond_wait_backout; - - /* Unlock the mutex: */ - if (mutex_locked && - ((rval = _mutex_cv_unlock(mutex)) != 0)) { - /* - * Cannot unlock the mutex; remove the - * running thread from the condition - * variable queue: - */ - cond_queue_remove(*cond, curthread); - curthread->sigbackout = NULL; - - /* Check for no more waiters: */ - if (TAILQ_FIRST(&(*cond)->c_queue) == NULL) - (*cond)->c_mutex = NULL; - } else { - /* - * Don't unlock the mutex the next - * time through the loop (if the - * thread has to be requeued after - * handling a signal). - */ - mutex_locked = 0; - - /* - * This thread is active and is in a - * critical region (holding the cv - * lock); we should be able to safely - * set the state. - */ - THR_LOCK_SWITCH(curthread); - THR_SET_STATE(curthread, PS_COND_WAIT); - - /* Remember the CV: */ - curthread->data.cond = *cond; - - /* Unlock the CV structure: */ - THR_LOCK_RELEASE(curthread, - &(*cond)->c_lock); - - /* Schedule the next thread: */ - _thr_sched_switch_unlocked(curthread); - - /* - * XXX - This really isn't a good check - * since there can be more than one - * thread waiting on the CV. Signals - * sent to threads waiting on mutexes - * or CVs should really be deferred - * until the threads are no longer - * waiting, but POSIX says that signals - * should be sent "as soon as possible". - */ - done = (seqno != (*cond)->c_seqno); - if (done && !THR_IN_CONDQ(curthread)) { - /* - * The thread is dequeued, so - * it is safe to clear this. - */ - curthread->data.cond = NULL; - curthread->sigbackout = NULL; - check_continuation(curthread, - NULL, mutex); - return (_mutex_cv_lock(mutex)); - } - - /* Relock the CV structure: */ - THR_LOCK_ACQUIRE(curthread, - &(*cond)->c_lock); - - /* - * Clear these after taking the lock to - * prevent a race condition where a - * signal can arrive before dequeueing - * the thread. - */ - curthread->data.cond = NULL; - curthread->sigbackout = NULL; - - done = (seqno != (*cond)->c_seqno); - - if (THR_IN_CONDQ(curthread)) { - cond_queue_remove(*cond, - curthread); - - /* Check for no more waiters: */ - if (TAILQ_FIRST(&(*cond)->c_queue) == NULL) - (*cond)->c_mutex = NULL; - } - - if (curthread->timeout != 0) { - /* The wait timedout. */ - rval = ETIMEDOUT; - } - } - } - break; - - /* Trap invalid condition variable types: */ - default: - /* Return an invalid argument error: */ - rval = EINVAL; - break; - } - - check_continuation(curthread, *cond, - mutex_locked ? NULL : mutex); - } while ((done == 0) && (rval == 0)); - - /* Unlock the condition variable structure: */ - THR_LOCK_RELEASE(curthread, &(*cond)->c_lock); - - if (mutex_locked == 0) - _mutex_cv_lock(mutex); - - /* Return the completion status: */ - return (rval); + return cond_wait_common(cond, mutex, abstime); } int @@ -575,74 +230,59 @@ struct pthread *curthread = _get_curthread(); int ret; + if (abstime == NULL) + return (EINVAL); _thr_cancel_enter(curthread); ret = _pthread_cond_timedwait(cond, mutex, abstime); _thr_cancel_leave(curthread, 1); return (ret); } - -int -_pthread_cond_signal(pthread_cond_t * cond) +static int +cond_signal_common(pthread_cond_t *cond, int broadcast) { struct pthread *curthread = _get_curthread(); - struct pthread *pthread; - long tid = -1; int rval = 0; - THR_ASSERT(curthread->locklevel == 0, - "cv_timedwait: locklevel is not zero!"); - if (cond == NULL) - rval = EINVAL; - /* - * If the condition variable is statically initialized, perform dynamic - * initialization. - */ - else if (*cond != NULL || (rval = init_static(curthread, cond)) == 0) { - /* Lock the condition variable structure: */ - THR_LOCK_ACQUIRE(curthread, &(*cond)->c_lock); + /* + * If the condition variable is statically initialized, perform dynamic + * initialization. + */ + if (__predict_false(*cond == NULL && + (rval = init_static(curthread, cond)) != 0)) + return (rval); - /* Process according to condition variable type: */ - switch ((*cond)->c_type) { - /* Fast condition variable: */ - case COND_TYPE_FAST: - /* Increment the sequence number: */ - (*cond)->c_seqno++; + /* Lock the condition variable structure */ + rval = UMTX_LOCK(&(*cond)->c_lock, curthread->tid); + if (__predict_false(rval)) + return (rval); - /* - * Wakeups have to be done with the CV lock held; - * otherwise there is a race condition where the - * thread can timeout, run on another KSE, and enter - * another blocking state (including blocking on a CV). - */ - if ((pthread = TAILQ_FIRST(&(*cond)->c_queue)) - != NULL) { - THR_THREAD_LOCK(curthread, pthread); - cond_queue_remove(*cond, pthread); - pthread->sigbackout = NULL; - tid = _thr_setrunnable_unlocked(pthread); - THR_THREAD_UNLOCK(curthread, pthread); - } - /* Check for no more waiters: */ - if (TAILQ_FIRST(&(*cond)->c_queue) == NULL) - (*cond)->c_mutex = NULL; + while ((*cond)->c_count) { + /* umtx_wake returns number of threads resumed */ + rval = umtx_wake(&(*cond)->c_count, + broadcast ? (*cond)->c_count : 1); + if (rval > 0) { + /* some threads were resumed. */ + (*cond)->c_count -= rval; + rval = 0; + } else if (rval == 0) { + (*cond)->c_count = 0; break; - - /* Trap invalid condition variable types: */ - default: - /* Return an invalid argument error: */ - rval = EINVAL; + } else { + rval = errno; break; } - - /* Unlock the condition variable structure: */ - THR_LOCK_RELEASE(curthread, &(*cond)->c_lock); - if (tid != -1) - thr_wake(tid); + if (!broadcast) + break; } + umtx_unlock(&(*cond)->c_lock, curthread->tid); + return (rval); +} - /* Return the completion status: */ - return (rval); +int +_pthread_cond_signal(pthread_cond_t * cond) +{ + return cond_signal_common(cond, 0); } __strong_reference(_pthread_cond_signal, _thr_cond_signal); @@ -650,62 +290,7 @@ int _pthread_cond_broadcast(pthread_cond_t * cond) { - struct pthread *curthread = _get_curthread(); - struct pthread *pthread; - long tid = -1; - int rval = 0; - - THR_ASSERT(curthread->locklevel == 0, - "cv_timedwait: locklevel is not zero!"); - if (cond == NULL) - rval = EINVAL; - /* - * If the condition variable is statically initialized, perform dynamic - * initialization. - */ - else if (*cond != NULL || (rval = pthread_cond_init(cond, NULL)) == 0) { - /* Lock the condition variable structure: */ - THR_LOCK_ACQUIRE(curthread, &(*cond)->c_lock); - - /* Process according to condition variable type: */ - switch ((*cond)->c_type) { - /* Fast condition variable: */ - case COND_TYPE_FAST: - /* Increment the sequence number: */ - (*cond)->c_seqno++; - - /* - * Enter a loop to bring all threads off the - * condition queue: - */ - while ((pthread = TAILQ_FIRST(&(*cond)->c_queue)) - != NULL) { - THR_THREAD_LOCK(curthread, pthread); - cond_queue_remove(*cond, pthread); - pthread->sigbackout = NULL; - tid = _thr_setrunnable_unlocked(pthread); - THR_THREAD_UNLOCK(curthread, pthread); - if (tid != -1) - thr_wake(tid); - } - - /* There are no more waiting threads: */ - (*cond)->c_mutex = NULL; - break; - - /* Trap invalid condition variable types: */ - default: - /* Return an invalid argument error: */ - rval = EINVAL; - break; - } - - /* Unlock the condition variable structure: */ - THR_LOCK_RELEASE(curthread, &(*cond)->c_lock); - } - - /* Return the completion status: */ - return (rval); + return cond_signal_common(cond, 1); } __strong_reference(_pthread_cond_broadcast, _thr_cond_broadcast); @@ -714,101 +299,4 @@ check_continuation(struct pthread *curthread, struct pthread_cond *cond, pthread_mutex_t *mutex) { - if ((curthread->interrupted != 0) && - (curthread->continuation != NULL)) { - if (cond != NULL) - /* Unlock the condition variable structure: */ - THR_LOCK_RELEASE(curthread, &cond->c_lock); - /* - * Note that even though this thread may have been - * canceled, POSIX requires that the mutex be - * reaquired prior to cancellation. - */ - if (mutex != NULL) - _mutex_cv_lock(mutex); - curthread->continuation((void *) curthread); - PANIC("continuation returned in pthread_cond_wait.\n"); - } -} - -static void -cond_wait_backout(void *arg) -{ - struct pthread *curthread = (struct pthread *)arg; - pthread_cond_t cond; - - cond = curthread->data.cond; - if (cond != NULL) { - /* Lock the condition variable structure: */ - THR_LOCK_ACQUIRE(curthread, &cond->c_lock); - curthread->data.cond = NULL; - - /* Process according to condition variable type: */ - switch (cond->c_type) { - /* Fast condition variable: */ - case COND_TYPE_FAST: - cond_queue_remove(cond, curthread); - - /* Check for no more waiters: */ - if (TAILQ_FIRST(&cond->c_queue) == NULL) - cond->c_mutex = NULL; - break; - - default: - break; - } - - /* Unlock the condition variable structure: */ - THR_LOCK_RELEASE(curthread, &cond->c_lock); - } - /* No need to call this again. */ - curthread->sigbackout = NULL; -} - -/* - * Remove a waiting thread from a condition queue in descending priority - * order. - */ -static inline void -cond_queue_remove(pthread_cond_t cond, struct pthread *pthread) -{ - /* - * Because pthread_cond_timedwait() can timeout as well - * as be signaled by another thread, it is necessary to - * guard against removing the thread from the queue if - * it isn't in the queue. - */ - if (THR_IN_CONDQ(pthread)) { - TAILQ_REMOVE(&cond->c_queue, pthread, sqe); - THR_CONDQ_CLEAR(pthread); - } -} - -/* - * Enqueue a waiting thread to a condition queue in descending priority - * order. - */ -static inline void -cond_queue_enq(pthread_cond_t cond, struct pthread *pthread) -{ - struct pthread *tid = TAILQ_LAST(&cond->c_queue, cond_head); - - THR_ASSERT(!THR_IN_SYNCQ(pthread), - "cond_queue_enq: thread already queued!"); - - /* - * For the common case of all threads having equal priority, - * we perform a quick check against the priority of the thread - * at the tail of the queue. - */ - if ((tid == NULL) || (pthread->active_priority <= tid->active_priority)) - TAILQ_INSERT_TAIL(&cond->c_queue, pthread, sqe); - else { - tid = TAILQ_FIRST(&cond->c_queue); - while (pthread->active_priority <= tid->active_priority) - tid = TAILQ_NEXT(tid, sqe); - TAILQ_INSERT_BEFORE(tid, pthread, sqe); - } - THR_CONDQ_SET(pthread); - pthread->data.cond = cond; }
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200412251312.iBPDCP35037106>