Futex

A futex is a word of ordinary memory and one system call that does two things with it: sleep if this word still holds what I expect, and wake one thread sleeping on it. Linux 2.5.7 shipped the first version in March 2002. A lock built on one need ask the kernel for nothing until two threads want it at once, and getting that right is harder than it sounds: Ulrich Drepper wrote a paper called Futexes Are Tricky and builds a mutex three times in it. This page runs a model of all three through every interleaving of up to four threads, counts the atomic operations and system calls each one makes, and lands on his table, whose cells fit only if it counts two different ways.

New to locks that sleep? Start here

A mutex is a lock one thread holds at a time. A thread that finds it taken has two choices: keep trying, which burns a processor doing nothing useful, or go to sleep and be woken when the lock is free. Sleeping is the polite choice, and only the operating system can do it, so it costs a system call: a deliberate trip into the kernel and back.

Most of the time nobody else wants the lock. A mutex that made a system call on every lock and unlock would pay that trip for nothing almost every time. The trick is to keep the lock's state in ordinary memory, change it with single atomic instructions, the kind Compare and Swap is about, and enter the kernel only when a thread really has to sleep or really has somebody to wake.

A futex is the kernel's half of that bargain, and it is one system call that does two things to one word of memory: sleep if this word still holds the value I expect, or wake one thread sleeping on it. The hard part is the program's half, which this page builds three times.

Two things at once

A program you write reads top to bottom, one step after the last. Once two of them run at the same time, that stops being true of the pair: their steps interleave, in an order nobody chose and nothing wrote down.

The hard part is that the order is not random so much as unconstrained. Any interleaving the hardware permits is one you have to treat as possible, even if no test you ever run happens to produce it: nothing promises that a scheduler will eventually choose it, and nothing promises it will not, on someone else's machine, months later, on the run you were not watching. So the machines in this topic are not about making the right order happen. They are about which orders are possible, which of those are wrong, and what it costs to rule them out.

The machine for this idea on its own is Race, if you would rather press it than read about it.

Machines here that come first: The Semaphore, Compare and Swap, The System Call.

Three mutexes, one word of memory, and the system calls they skip

1 The first mutex: one counter, and an unlock that always calls the kernel

The kernel’s half is small. futex_wait (&val, v) puts the caller to sleep only if val still holds v, and otherwise returns at once with EWOULDBLOCK. futex_wake (&val, 1) wakes one thread sleeping on that word, if there is one. The one guarantee that makes it work is that the check and the sleep are a single step. Drepper says the test of the value “must be atomic, too”, and the manual page today says “The loading of the futex word’s value, the comparison of that value with the expected value, and the actual blocking will happen atomically”.

Everything else is the program’s. Drepper’s first mutex, from section 4 of the paper, keeps a count in the word: nought is free, anything else is taken. lock () adds one, and if the old value was not nought it sleeps on the value it made. unlock () stores nought and wakes one sleeper.

void lock () {
  int c;
  while ((c = atomic_inc (val)) != 0)
    futex_wait (&val, c + 1); }
void unlock () {
  val = 0; futex_wake (&val, 1); }

The marked row in the table below, an unlock with nobody waiting, is the cost a futex exists to avoid, and this mutex pays it anyway. Its unlock cannot tell whether anybody is waiting, so it asks the kernel every time. Run on a real Linux kernel with the real system call, one thread taking and releasing each mutex a thousand times made 1,000 futex calls with this one and none with the other two, counted by the program as it ran and again from outside by strace.

callatomic operationssystem callsthe paper prints
lock (), nobody holding it101 and 0
unlock (), nobody waiting010 and 1
lock (), held, waits once21see step 4
unlock (), somebody waiting010 and 1

states the threads can reach
41
places an unlock calls futex_wake
14
and nobody is asleep to wake
12

With 2 threads, the model reaches 41 states. In 12 of the 14 places where an unlock calls futex_wake, nobody is asleep to be woken. Each of those is a system call made for nothing.

2 Two waiters on that counter can keep each other awake until it wraps

The first mutex has a worse problem than the wasted call, and Drepper calls it “quite serious in some situations but very hard to spot”. Thread 1 holds the lock. Two more threads each add one to the word and ask to sleep on the value they made. But each increment changes the word under the other thread’s futex_wait, so the kernel sends both straight back with EWOULDBLOCK, and they add one again. “This process can be continued ad infinitum.”

Not quite, because a word cannot count forever. Nobody can enumerate a 32-bit counter, so the counter here is narrow and its width is yours to set. Thread 1 is held still inside the lock, the other two may do anything the model allows, and the longest run of futex_waits that fail is counted.

longest run of failed futex_waits, lock held
2
2 to the width, minus 2
2
states with two threads inside the lock
16
states, with thread 1 held still
82

The shortest schedule that ends with two threads inside, in the paper’s own calls:

stepthreaddoesval after
0thread 1holds the lock, and is held still1
1thread 2atomic_inc returns 12
2thread 3atomic_inc returns 23
3thread 2futex_wait (&val, 2) returns EWOULDBLOCK3
4thread 2atomic_inc returns 30
5thread 3futex_wait (&val, 3) returns EWOULDBLOCK0
6thread 3atomic_inc returns 01

The shortest schedule the model finds from thread 1 holding the lock to two threads inside it: 6 steps.

With the lock held and nobody releasing it, the two waiters can fail 2 futex_waits in a row, which is 2 to the 2 minus 2. Then the counter reads nought, the next increment returns nought, and a second thread walks into the lock beside the one holding it. On a 32-bit int the same path is 4,294,967,294 failed waits long: the formula, not a count, because nobody can enumerate it.

So the first bug feeds the second. Drepper’s second bug is the counter running out, after which “magically the variable is free”, and he asks when a futex_wait can return without the mutex having been released: “One example is the first bug above.” With only one waiter and no signals it cannot happen, because a failed wait then means the holder has already let go. It takes a second waiter to keep the first one awake. The first bug was “present in some form for many months” in the NPTL thread library, and fixing it, he reports, one real application on a four-processor machine “got sped up eight to ten times”.

3 The second mutex: three values, and the wake it is allowed to skip

The second mutex stops counting. The word holds one of three values: nought is free, 1 is locked with no waiters, 2 is locked with waiters, perhaps. A thread that finds the lock taken marks it 2 before it sleeps. Now an unlock that finds 1 knows nobody is asleep and skips the system call, and a waiter’s futex_wait always expects 2, which nobody changes while the lock is held.

void lock () {
  int c;
  if ((c = cmpxchg (val, 0, 1)) != 0)
    do {
      if (c == 2
          || cmpxchg (val, 1, 2) != 0)
        futex_wait (&val, 2);
    } while ((c = cmpxchg (val, 0, 2))
             != 0);
}
void unlock () {
  if (atomic_dec (val) != 1) {
    val = 0;
    futex_wake (&val, 1);
  }
}

states the threads can reach
1,002
states with two threads inside the lock
0
longest run of failed futex_waits, lock held
0
states with a sleeper nothing running will wake
0
system calls in an uncontended unlock
0

With 3 threads: nobody else inside, no futex_wait that fails while the lock is held, and no sleeper left behind, over 1,002 states. Uncontended, unlock makes 0 system calls.

The button changes one constant: the value the loop writes when it finally takes the lock. Drepper’s reason for writing 2 is “because we do not know any better”. A thread that has just been woken cannot know whether others are still asleep behind it, and “being wrong in guessing sooner or later means running into a deadlock”. A sleeper is stranded when it is asleep, the lock is free, and every other thread has finished with it, which is his own requirement for a mutex turned upside down.

The shortest schedule to a stranded sleeper, when there is one:

stepthreaddoesval after
no such state: every sleeper here can still be woken by a thread that is running

The price of guessing 2 is paid the other way round. Sometimes an unlock finds 2, calls the kernel, and there is nobody left to wake. Drepper says so, and it is a system call spent to make sure no thread is ever stranded.

4 The third mutex, and a cost table whose cells fit two counts

The third mutex is the second with the two compares in its loop replaced by an exchange, which writes 2 whatever the word held. The revision history in the paper dates it to February 2004, four months after the first draft, and it comes with a second table comparing it against the second mutex.

void lock () {
  int c;
  if ((c = cmpxchg (val, 0, 1)) != 0) {
    if (c != 2)
      c = xchg (val, 2);
    while (c != 0) {
      futex_wait (&val, 2);
      c = xchg (val, 2);
    }
  }
}
void unlock () {
  if (atomic_dec (val) != 1) {
    val = 0;
    futex_wake (&val, 1);
  }
}

Both tables print a contended lock’s atomic operations as two numbers. The second is the trip round the loop again, which in his words “represents the additional cost for the function call which has to be paid” when futex_wait returns and the thread has to go back to sleep. A stacked cell means “use the upper number, otherwise the lower number”: upper when there are waiters already. What the first number counts, the paper does not say. The model counts it both ways: as far as the first futex_wait, and the whole call when it waits exactly once.

mutexprintedto first waitwhole callmatches
the first mutex112before
the second mutex, waiters already212whole
the second mutex, no waiters yet323whole
the third mutex, waiters already112before
the third mutex, no waiters yet223before

Every printed cell is a number the model produces, but not under one count. The paper does not say what its first number counts, so what follows is this page's reading. The second mutex's column fits the whole call, including the compare that finally takes the lock; the other two fit only as far as the first futex_wait. Counted the second mutex's way, the first mutex is 2 and not 1, so the second makes 2 or 3 atomic operations against the first's 2, not “2 to 3 times” as many. And counted either way, the third mutex's first trip costs exactly what the second's does.

And the second number, what one more trip round the loop adds, every value the cost census reaches before its cap:

mutexwaiters alreadyno waiters yetprinted
the second mutex1, 2, 3, 41, 2, 3, 41 and 2
the third mutex111 and 1

So the third mutex's saving is in the trips that fail: one exchange, always, where the second can need two compares, and more while other threads keep taking and dropping the lock between them. The model reaches extra trips of 1, 2, 3, 4 for the second, a list the census cuts off at 4 atomics and not the end of what the model can do, and 1 for the third.

These ran in this browser when the page loaded. Each claim, whether it held, and the number behind it.

Each claim, whether it held, and the values behind it
claimheldmeasured
uncontended, every cell of the paper's first table comes out of the model: the first mutex's unlock makes 1 system call and the second's makes 0yeseight cells of eight
the third mutex's uncontended lock and unlock cost exactly what the second's doyeslock 1 atomic, unlock 1 atomic, no system call
the second and third mutexes never let two threads in, never strand a sleeper, and never leave the values 0, 1 and 2, with two, three or four threadsyes6 state spaces enumerated
with the lock held and two threads trying, the first mutex can fail 2 to the W minus 2 futex_waits in a row on a W-bit counter, and then two threads are insideyesevery width from 2 to 6 bits; at 32 bits the formula gives 4,294,967,294
with the lock held and three threads trying, the second and third mutexes fail no futex_wait at allyesmutex2: 0 over 64 states; mutex3: 0 over 64 states
with only two threads the first mutex never counts past 2 and never lets two in, at any width triedyes2 to 6 bits, 41 states each: it takes a second waiter to keep the first awake
retaking the lock with 1 instead of 2 strands a sleeper in 3 states with three threads; retaking with 2 strands noneyesand with two threads the change strands nobody: 0 states
contended, every system-call cell of the paper's tables comes out of the model, and so does every unlockyesone futex_wait per trip, one futex_wake per contended unlock
every contended atomic-operation cell matches the model, but not under one count: the second mutex's column counts the whole call, the other two count up to the first futex_waityesmutex printed 1, before 1, whole 2; mutex2 upper printed 2, before 1, whole 2; mutex2 lower printed 3, before 2, whole 3; mutex3 upper printed 1, before 1, whole 2; mutex3 lower printed 2, before 2, whole 3
counted either way, a contended lock's first trip costs the third mutex exactly what it costs the second, with or without waiters alreadyyeswaiters already: before the wait 1 and 1, whole call 2 and 2; no waiters yet: before the wait 2 and 2, whole call 3 and 3
an extra trip round the loop costs the third mutex exactly one atomic; the second one or two in either case, and more while other threads keep slipping inyessecond: {1, 2, 3, 4} and {1, 2, 3, 4}; third: {1} and {1}
the cost census followed thread 1 through every lock call of up to 3 futex_waits and 4 atomics between two of them, with three threads, and counts the calls that went pastyesmutex: 7 different costs, 99 ways for a call past the caps to finish; mutex2: 170 different costs, 2834 ways for a call past the caps to finish; mutex3: 8 different costs, 33 ways for a call past the caps to finish

What is real here, and what is not

The model's memory is sequentially consistent, and a real processor's is not

Every step here happens in one global order that every thread agrees on. Real processors reorder loads and stores, and Memory Ordering shows one doing it. This page does not model that at all. The measurement on real hardware was made on one x86 machine, and it says nothing about what a processor that orders memory more weakly would make of the same C.

A sleeper wakes only when futex_wake picks it

A real futex_wait also returns on a signal or a timeout. The model has neither, so it contains no spurious wakeups at all. Drepper names the signal as the second route to the first mutex's overflow, one that “cannot be avoided”, and it would let a single waiter overflow the counter on its own. That route is left out, so with two threads this page finds no overflow, which is a statement about the model and not about a program receiving signals.

2002 is the futex, and the interface the paper uses is from May 2002

Franke, Russell and Kirkwood describe the futex as “integrated into the Linux kernel distribution version 2.5.7”, kernel/futex.c is in the 2.5.7 tree, and kernel.org’s own listing dates that release 18 March 2002. Its changelog carries the patch “Futexes IV (Fast Lightweight Userspace Semaphores)”. But that version had two operations, FUTEX_UP and FUTEX_DOWN, and kept the count in the kernel. The wait-if-still-equal interface Drepper programs against came in 2.5.18, whose changelog says it “changes futex semantics to a simple” sleep-if-equal call, and whose sys_futex takes the value and a timeout. Franke, Russell and Kirkwood’s Ottawa Linux Symposium paper of 2002 describes both. The futex(2) manual page dates the four-argument call to 2.5.40; the 2.5.18 source already has it, so this page goes by the source.

The paper is from 2003, and the third mutex is from 2004

The copy read here is version 1.6 of Futexes Are Tricky, dated 2011. Its own revision history gives the first draft as 2003-10-12 and the third mutex as an addition of 2004-02-22. The quotations and both tables are from version 1.6; the page does not claim the 2003 draft printed them in this form.

The counter is a few bits wide here, not thirty-two

The first mutex's counter is a control from 2 to 8 bits, because a 32-bit one has 4,294,967,296 values and cannot be enumerated. At every width checked, the longest run of failed waits is 2 to the width minus 2, found by search and held against that formula. The 32-bit figure the page prints is the formula, said to be the formula. Nothing here enumerated it.

Who counts what in the cost table is this page's reading, and it is labelled as one

The paper defines the second number in each contended cell and not the first. This page counts the first two ways and reports which way each printed cell agrees with. Both counts are the model's; the matching is arithmetic. That the second mutex's column uses one count and the other two use the other is an inference from that matching, not something the paper says. It is the reading under which every printed cell is right, and it is why the page does not call any cell wrong.

What one step is

An atomic instruction is one step, and so is each system call: the kernel’s check-and-sleep, and its wake of one chosen sleeper. Inside the kernel there is a lock on the hash bucket and a queue, and the model folds all of that into the one step, which is what the kernel’s own guarantee entitles it to. Which sleeper a wake picks is not promised, so every choice is a separate branch. The plain store of nought in unlock is one step and, as in the paper, is not counted as atomic.

What the real measurement is, and what it is not

A small C program transcribes the three mutexes from the paper, calls the real futex system call, and counts its own atomic operations and futex calls. strace logs every futex call from outside the process, and the test counts the log against the program, call for call, on the program’s own futex word. The run recorded with this page was made on Linux 7.0.0. It counts calls and measures no times. strace slows every system call it watches, which widens every race the page is about, so the contended runs make far more failed waits than an untraced program would. That is why only counts are used, and only to ask whether each real lock call cost something the model can reach. A few calls waited more times than the model follows: those are held only to a rule for their cost, and the longest of them are only counted.

Not the first lock that stays out of the kernel

The Ottawa paper says so itself: there were “several independent implementations” before the futex, and it compares one, ulocks, in its benchmarks. What can be said is narrower, and it is what this page says: the futex is the one that went into Linux, and Drepper’s abstract says “It is used in the modern thread library implementation”.

What this page leaves to its neighbours

Why a compare-and-swap loop retries, and what the retries cost under contention, is Compare and Swap. What a system call is, and why entering the kernel is a door rather than a jump, is The System Call. The count a futex is so often used to build is The Semaphore, and what a thread that goes to sleep costs the processor is Context Switch. None of it is re-derived here.

Sound: no

Asked and answered, so it does not get re-opened. Every number here is a count of states or of calls, and nothing in the model has a duration. A click per system call would decorate a number already printed.

Sources