2026-07-13
You've seen futexes as the foundation of every lock. But the plain FUTEX_WAIT op has two limitations that bit real systems hard: its timeout is relative (racy under clock changes), and a FUTEX_WAKE hits waiters in FIFO order without discrimination. FUTEX_WAIT_BITSET and FUTEX_WAKE_BITSET fix both — and they're what glibc actually calls under the hood.
The bitmask trick. Every waiter registers a 32-bit mask when it sleeps. Every waker specifies a 32-bit mask when it signals. A waiter is eligible for wakeup only if waiter_mask & waker_mask != 0. The kernel walks the hash bucket, tests the AND, and skips non-matching waiters without dequeuing them. Cost: one extra load and one AND per waiter scanned.
The absolute timeout. Unlike FUTEX_WAIT, the timespec is interpreted as an absolute time against CLOCK_MONOTONIC (or CLOCK_REALTIME if you set FUTEX_CLOCK_REALTIME in the op flags). This eliminates the classic bug: with a relative timeout, if your thread is preempted between computing "5ms from now" and entering the syscall, you sleep longer than intended. With absolute timeouts, the deadline is fixed at the call site.
Real-world example: glibc's pthread_cond_timedwait. Since glibc 2.25, condition variables use FUTEX_WAIT_BITSET exclusively. The bitmask distinguishes waiters from different "groups" (G1 vs G2 in the internal design), so pthread_cond_signal can wake exactly one waiter from the current group without also waking a straggler from the previous broadcast. Before the bitset op existed, glibc had to use a spurious-wakeup-and-recheck dance that leaked wakeups under contention.
The magic constant. If you don't care about selective wakeup, pass FUTEX_BITSET_MATCH_ANY (which is just 0xFFFFFFFF). This matches every waiter's mask and gives you plain FUTEX_WAKE semantics — but you still get the absolute-timeout benefit on the wait side. This is why you'll see 0xFFFFFFFF passed almost everywhere in glibc's futex calls even when nobody's using the bitmask feature.
Rule of thumb. If you're writing anything more sophisticated than a plain mutex — condvars, barriers, priority-tiered locks, or anything with a deadline — reach for FUTEX_WAIT_BITSET from the start. The kernel cost over plain FUTEX_WAIT is roughly one cache line's worth of extra work per waiter scanned, which is negligible compared to the wakeup itself (~2µs). The correctness win from absolute timeouts alone justifies it.
Gotcha. A mask of 0 is rejected with EINVAL on both wait and wake — the kernel enforces "no null bitmask" to prevent silent bugs where a caller forgot to initialize the mask field.
