Table of Contents

  1. Syscalls, memory, and your first therad
  2. The pointer to self and thread-local storage
  3. Futexes, mutexes, and memory sychronization
  4. Joining threads and dynamic initialization
  5. Cancellation
  6. Scheduling and task priority
  7. RW Locks
  8. Condition variables
  9. Final thoughts

Condition Variables

Condition variables are a mechanism used for signaling that a certain predicate has become true. The POSIX mechanism for handling them boils down to three functions: cond_wait, cond_signal and cond_broadcast. The first one causes the thread that calls it to wait. The second wakes a thread up so that it can verify whether the condition is true. The third wakes all the waiters up.

The waiter

 1tb_futex_lock(&cond->lock);
 2...
 3++cond->waiters;
 4int bseq = cond->broadcast_seq;
 5int futex = cond->futex;
 6tb_futex_unlock(&cond->lock);
 7
 8while(1) {
 9  st = SYSCALL3(__NR_futex, &cond->futex, FUTEX_WAIT, futex);
10  if(st == -EINTR)
11    continue;
12
13  tb_futex_lock(&cond->lock);
14  if(cond->signal_num) {
15    --cond->signal_num;
16    goto exit;
17  }
18
19  if(bseq != cond->broadcast_seq)
20    goto exit;
21  tb_futex_unlock(&cond->lock);
22}

The algorithm is as follows:

  1. We lock the internal lock.
  2. We remember the value of the broadcast sequence and the futex.
  3. In a loop, we wait for the futex.
  4. We go back to sleeping if the FUTEX_WAIT syscall was interrupted by a signal.
  5. We consume a signal, if we can, and exit.
  6. If there was a broadcast, we exit too.
  7. Otherwise, we go back to sleep.

Signal

We wake one of the threads up. We bump the value of the futex to prevent a deadlock. Then we bump the number of signals and wake the futex.

 1int tbthread_cond_signal(tbthread_cond_t *cond)
 2{
 3  tb_futex_lock(&cond->lock);
 4  if(cond->waiters == cond->signal_num)
 5    goto exit;
 6  ++cond->futex;
 7  ++cond->signal_num;
 8  SYSCALL3(__NR_futex, &cond->futex, FUTEX_WAKE, 1);
 9exit:
10  tb_futex_unlock(&cond->lock);
11  return 0;
12}

Broadcast

We wake all the waiters. The algorithm is essentially the same as for signal, except that, we bump the broadcast sequence number and wake all the threads instead of just one.

 1int tbthread_cond_broadcast(tbthread_cond_t *cond)
 2{
 3  tb_futex_lock(&cond->lock);
 4  if(!cond->waiters)
 5    goto exit;
 6  ++cond->futex;
 7  ++cond->broadcast_seq;
 8  SYSCALL3(__NR_futex, &cond->futex, FUTEX_WAKE, INT_MAX);
 9exit:
10  tb_futex_unlock(&cond->lock);
11  return 0;
12}

See the full patch at GitHub.

If you like this kind of content, you can subscribe to my newsletter, follow me on Twitter, or subscribe to my RSS channel.