about summary refs log tree commit diff
path: root/src/thread/pthread_cond_broadcast.c
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2011-09-26 00:25:13 -0400
committerRich Felker <dalias@aerifal.cx>2011-09-26 00:25:13 -0400
commit729d6368bdf9faa33299cdfa68efc7422af33bd7 (patch)
tree4d97dfde4d0eb7b73c34260335a4b785280cdba4 /src/thread/pthread_cond_broadcast.c
parentc11d1e696723f41d7873332e51fb6858b417fa5f (diff)
downloadmusl-729d6368bdf9faa33299cdfa68efc7422af33bd7.tar.gz
musl-729d6368bdf9faa33299cdfa68efc7422af33bd7.tar.xz
musl-729d6368bdf9faa33299cdfa68efc7422af33bd7.zip
redo cond vars again, use sequence numbers
testing revealed that the old implementation, while correct, was
giving way too many spurious wakeups due to races changing the value
of the condition futex. in a test program with 5 threads receiving
broadcast signals, the number of returns from pthread_cond_wait was
roughly 3 times what it should have been (2 spurious wakeups for every
legitimate wakeup). moreover, the magnitude of this effect seems to
grow with the number of threads.

the old implementation may also have had some nasty race conditions
with reuse of the cond var with a new mutex.

the new implementation is based on incrementing a sequence number with
each signal event. this sequence number has nothing to do with the
number of threads intended to be woken; it's only used to provide a
value for the futex wait to avoid deadlock. in theory there is a
danger of race conditions due to the value wrapping around after 2^32
signals. it would be nice to eliminate that, if there's a way.

testing showed no spurious wakeups (though they are of course
possible) with the new implementation, as well as slightly improved
performance.
Diffstat (limited to 'src/thread/pthread_cond_broadcast.c')
-rw-r--r--src/thread/pthread_cond_broadcast.c45
1 files changed, 18 insertions, 27 deletions
diff --git a/src/thread/pthread_cond_broadcast.c b/src/thread/pthread_cond_broadcast.c
index 7e5ea91c..bf6de048 100644
--- a/src/thread/pthread_cond_broadcast.c
+++ b/src/thread/pthread_cond_broadcast.c
@@ -1,48 +1,39 @@
 #include "pthread_impl.h"
 
-static void unlock(pthread_cond_t *c)
-{
-	a_dec(&c->_c_bcast);
-	if (c->_c_leavers) __wake(&c->_c_bcast, -1, 0);
-}
-
 int pthread_cond_broadcast(pthread_cond_t *c)
 {
 	pthread_mutex_t *m;
-	int w;
 
 	if (!c->_c_waiters) return 0;
-	a_inc(&c->_c_bcast);
-	if (!c->_c_waiters) {
-		unlock(c);
-		return 0;
-	}
 
-	a_store(&c->_c_block, 0);
+	a_inc(&c->_c_seq);
 
-	m = c->_c_mutex;
-
-	/* If mutex ptr is not available, simply wake all waiters. */
-	if (m == (void *)-1) {
-		unlock(c);
-		__wake(&c->_c_block, -1, 0);
+	/* If cond var is process-shared, simply wake all waiters. */
+	if (c->_c_mutex == (void *)-1) {
+		__wake(&c->_c_seq, -1, 0);
 		return 0;
 	}
 
+	/* Block waiters from returning so we can use the mutex. */
+	while (a_swap(&c->_c_lock, 1))
+		__wait(&c->_c_lock, &c->_c_lockwait, 1, 1);
+	if (!c->_c_waiters)
+		goto out;
+	m = c->_c_mutex;
+
 	/* Move waiter count to the mutex */
-	for (;;) {
-		w = c->_c_waiters;
-		a_fetch_add(&m->_m_waiters, w);
-		if (a_cas(&c->_c_waiters, w, 0) == w) break;
-		a_fetch_add(&m->_m_waiters, -w);
-	}
+	a_fetch_add(&m->_m_waiters, c->_c_waiters);
+	a_store(&c->_c_waiters, 0);
 
 	/* Perform the futex requeue, waking one waiter unless we know
 	 * that the calling thread holds the mutex. */
-	__syscall(SYS_futex, &c->_c_block, FUTEX_REQUEUE,
+	__syscall(SYS_futex, &c->_c_seq, FUTEX_REQUEUE,
 		!m->_m_type || (m->_m_lock&INT_MAX)!=pthread_self()->tid,
 		INT_MAX, &m->_m_lock);
 
-	unlock(c);
+out:
+	a_store(&c->_c_lock, 0);
+	if (c->_c_lockwait) __wake(&c->_c_lock, 1, 0);
+
 	return 0;
 }