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.
| call | atomic operations | system calls | the paper prints |
|---|---|---|---|
| lock (), nobody holding it | 1 | 0 | 1 and 0 |
| unlock (), nobody waiting | 0 | 1 | 0 and 1 |
| lock (), held, waits once | 2 | 1 | see step 4 |
| unlock (), somebody waiting | 0 | 1 | 0 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:
| step | thread | does | val after |
|---|---|---|---|
| 0 | thread 1 | holds the lock, and is held still | 1 |
| 1 | thread 2 | atomic_inc returns 1 | 2 |
| 2 | thread 3 | atomic_inc returns 2 | 3 |
| 3 | thread 2 | futex_wait (&val, 2) returns EWOULDBLOCK | 3 |
| 4 | thread 2 | atomic_inc returns 3 | 0 |
| 5 | thread 3 | futex_wait (&val, 3) returns EWOULDBLOCK | 0 |
| 6 | thread 3 | atomic_inc returns 0 | 1 |
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:
| step | thread | does | val 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.
| mutex | printed | to first wait | whole call | matches |
|---|---|---|---|---|
| the first mutex | 1 | 1 | 2 | before |
| the second mutex, waiters already | 2 | 1 | 2 | whole |
| the second mutex, no waiters yet | 3 | 2 | 3 | whole |
| the third mutex, waiters already | 1 | 1 | 2 | before |
| the third mutex, no waiters yet | 2 | 2 | 3 | before |
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:
| mutex | waiters already | no waiters yet | printed |
|---|---|---|---|
| the second mutex | 1, 2, 3, 4 | 1, 2, 3, 4 | 1 and 2 |
| the third mutex | 1 | 1 | 1 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.
| claim | held | measured |
|---|---|---|
| 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 0 | yes | eight cells of eight |
| the third mutex's uncontended lock and unlock cost exactly what the second's do | yes | lock 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 threads | yes | 6 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 inside | yes | every 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 all | yes | mutex2: 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 tried | yes | 2 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 none | yes | and 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 unlock | yes | one 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_wait | yes | mutex 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 already | yes | waiters 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 in | yes | second: {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 past | yes | mutex: 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
- Ulrich Drepper, Futexes Are Tricky, version 1.6, 2011; first draft 2003-10-12. The three mutexes, the two cost tables, the first mutex's two bugs and the reason the second retakes the lock with 2. Every program on this page is transcribed from it.
- Hubertus Franke, Rusty Russell and Matthew Kirkwood, Fuss, Futexes and Furwocks: Fast Userlevel Locking in Linux, Ottawa Linux Symposium, 2002. The 2.5.7 implementation and the change to wait and wake.
- kernel.org's own listing of the 2.5 series, which dates linux-2.5.7 and its changelog to 18 March 2002.
- The changelog for Linux 2.5.7, with Rusty Russell's patch, Futexes IV.
- The changelog for Linux 2.5.18, where futex semantics become sleep if this address equals this value.
- kernel/futex.c as released in 2.5.7, from the Linux history tree: two operations, FUTEX_UP and FUTEX_DOWN.
- kernel/futex.c as released in 2.5.18: FUTEX_WAIT, FUTEX_WAKE and EWOULDBLOCK, the interface the paper uses.
- The futex(2) manual page, for the kernel's atomicity guarantee in the words it is documented in today.
- Logical Art, the studio this belongs to.