futex_waitv(2)
π futex_waitv(2) β the "wait on a whole lil squad of futexes" syscall π
okay bestie, this one's a <syscall> from the System Calls Manual and honestly? she's giving parallel-waiting main character energy. sit down, hydrate, let's go π«Ά
β¨ NAME β¨
futex_waitv β wait for a FUTEX_WAKE operation on a vector of futexes. yes, a whole vector. multiple futexes. at once. iconic behavior π
π LIBRARY π
Standard C library (libc, -lc) πΈ
π SYNOPSIS π
#include <linux/futex.h> /* Definition of FUTEX* constants */
#include <sys/syscall.h> /* Definition of SYS_* constants */
#include <unistd.h>
#include <time.h>
long syscall(unsigned int n;
SYS_futex_waitv, struct futex_waitv waiters[n],
unsigned int n, unsigned int flags,
const struct timespec *_Nullable timeout, clockid_t clockid);
#include <linux/futex.h>
struct futex_waitv {
u64 val; /* Expected value at uaddr */
u64 uaddr; /* User address to wait on */
u32 flags; /* Flags for this waiter */
u32 __reserved; /* Align to u64 */
};
π¦ DESCRIPTION π¦
So this syscall implements the FUTEX_WAIT_MULTIPLE operation. it's basically a synchronous, atomic, parallel FUTEX_WAIT(2const) or FUTEX_WAIT_PRIVATE β except it works on up to FUTEX_WAITV_MAX futex words at the same time πβ¨
want the big picture on futexes? go read futex(7). want the general interface? that's futex(2). want all the general minutiae of futex waiting? also the page above. iykyk π
Here's what it actually does: it tests that the values at the futex words waiters[].uaddr[0] still contain their respective expected values waiters[].val. if they all still match, it goes to sleep waiting for a FUTEX_WAKE(2const) operation on any of the futex words β and then it returns the index of a waiter whose futex got woken. slay.
If the thread starts to sleep, it counts as a waiter on all the given futex words. and if any of the futex values don't match their respective waiters[].val, the call fails immediately with the error EAGAIN. no waiting, no vibes, just no π«
If timeout is NULL, the call blocks indefinitely (forever era π). otherwise, *timeout specifies a deadline measured against clock clockid. this interval gets rounded up to the system clock granularity, and it's guaranteed not to expire early. no cap.
The clocks you can use are:
CLOCK_MONOTONICCLOCK_REALTIME
The futex words to monitor are given by struct futex_waitv, whose fields are analogous to FUTEX_WAIT(2const) parameters β except .__reserved must be 0 and .flags must contain exactly one size flag, ORed with some other flags. rules are rules bestie β¨
FUTEX2_SIZE_U32β.valand.uaddr[]are 32-bit unsigned integers.FUTEX2_SIZE_U8,FUTEX2_SIZE_U16,FUTEX2_SIZE_U64β these are defined, but not supported. you'll getEINVAL. πFUTEX2_NUMAβ the futex word is followed by another word of the same size (.uaddrpoints touintN_t[2]rather thanuintN_t. the word is given by.uaddr[1]), which can be eitherFUTEX_NO_NODE(all bits set) or a NUMA node number.
If the NUMA word is FUTEX_NO_NODE, the node number of the processor the syscall executes on is written to it. (except that in an EINVAL or EFAULT condition, this happens to all waiters whose .flags have FUTEX2_NUMA set.)
Futexes are placed on the NUMA node given by the NUMA word. futexes without this flag are placed on a random node. chaotic but valid π
FUTEX2_PRIVATEβ by default, the futex is shared (likeFUTEX_WAIT(2const)) and can be accessed by multiple processes. this flag instead waits on a private futex word, where all users must use the same virtual memory map (likeFUTEX_WAIT_PRIVATE; most often that means they're part of the same process). private futexes are faster than shared ones. faster = better, periodt π₯
Programs should assign to .uaddr by casting a pointer to uintptr_t. yes, the cast is mandatory, don't fight it π
π«Ά RETURN VALUE π«Ά
It returns an index to an arbitrary entry in waiters corresponding to some woken-up futex. and heads up: this implies absolutely no information about the other waiters. she said "one of them woke, that's all you're getting" π
On error, -1 is returned, and errno is set to indicate the error.
π₯ ERRORS π₯
EFAULTβwaiterspoints outside the accessible address space.EFAULTβtimeoutis notNULLand points outside the accessible address space.EFAULTβ anywaiters[].uaddrfield points outside the accessible address space.EINVALβ anywaiters[].uaddrfield does not point to a valid object β that is, the address is not aligned appropriately for the specifiedFUTEX2_SIZE_*.EINVALβflagswas not0.EINVALβnwas not in the range[1, FUTEX_WAITV_MAX].EINVALβtimeoutwas notNULLandclockidwas not a valid clock.EINVALβ*timeoutis denormal (before epoch ortv_nsecnot in the range[0, 999'999'999]).EINVALβ anywaiters[].flagsfield contains an unknown flag.EINVALβ anywaiters[].flagsfield does not contain exactly one size flag, or it contains an unsupported one.EINVALβ anywaiters[].__reservedfield is not0.EINVALβ anywaiters[].valuefield has more bits set than permitted than the size flags.EINVALβFUTEX2_NUMAwas set inwaiters[].flags, and the NUMA word (which is the same size as the futex word) is too small to contain the highest possible index of a NUMA domain (for example,FUTEX2_SIZE_U8and there are at least 255 possible NUMA domains).EINVALβFUTEX2_NUMAwas set inwaiters[].flags, and the NUMA word is larger than the maximum possible NUMA node and notFUTEX_NO_NODE.ETIMEDOUTβ the timeout elapsed before any futex was woken.EAGAINorEWOULDBLOCKβ the value in.uaddr[0]was not equal to the expected value.valat the time of the call.EINTRβ the operation was interrupted by a signal (seesignal(7)).
π STANDARDS π
Linux. obviously π
β HISTORY β
Linux 5.16.
π§ EXAMPLES π§
okay so this program right here runs a linear-time operation on 10 threads, shows the results in real time, and waits at most 1 second for each new result. the first 3 threads operate on the same data (so they complete in the same time). the ! marks the futex that woke up each futex_waitv() π
$ ./futex_waitv
153 153 153 237 100 245 177 127 215 61
122!
200!
254!
306 306!
306!
354!
430!
474!
490!
futex_waitv: my_futex_waitv: Connection timed out
and here's the whole thing, bestie:
#include <err.h>
#include <errno.h>
#include <linux/futex.h>
#include <pthread.h>
#include <stdatomic.h>
#include <stdcountof.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
static inline long
my_futex_wake_private(_Atomic uint32_t *uaddr, uint32_t val)
{
return syscall(SYS_futex, uaddr, FUTEX_WAKE_PRIVATE, val);
}
static inline long
my_futex_waitv(unsigned int n;
struct futex_waitv waiters[n], unsigned int n,
unsigned int flags, const struct timespec *timeout,
clockid_t clockid)
{
return syscall(SYS_futex_waitv, waiters, n, flags, timeout, clockid);
}
void *
worker(void *arg)
{
_Atomic uint32_t *futex = arg;
usleep(*futex * 10000);
*futex *= 2;
my_futex_wake_private(futex, 1);
return NULL;
}
int
main(void)
{
_Atomic uint32_t futexes[10];
uint8_t init[countof(futexes)];
struct futex_waitv waiters[countof(futexes)] = {};
if (getentropy(init, sizeof(init)))
err(EXIT_FAILURE, "getentropy");
init[0] = init[1] = init[2];
for (int i = 0; i < countof(futexes); ++i) {
printf("%w8u\t", init[i]);
atomic_init(&futexes[i], init[i]);
pthread_create(&(pthread_t){}, NULL, worker, &futexes[i]);
}
putchar('\n');
for (int i = 0; i < countof(futexes); ++i) {
waiters[i].val = futexes[i];
waiters[i].uaddr = (uintptr_t) &futexes[i];
waiters[i].flags = FUTEX2_SIZE_U32 | FUTEX2_PRIVATE;
}
for (;;) {
int woke;
struct timespec timeout;
clock_gettime(CLOCK_MONOTONIC, &timeout);
timeout.tv_sec += 1;
woke = my_futex_waitv(waiters, countof(futexes), 0, &timeout, CLOCK_MONOTONIC);
if (woke == -1 && (errno != EAGAIN && errno != EWOULDBLOCK))
err(EXIT_FAILURE, "my_futex_waitv");
for (int i = 0; i < countof(futexes); ++i) {
if (futexes[i] != waiters[i].val)
printf("%w32u%s", futexes[i], i == woke ? "!" : "");
putchar('\t');
}
putchar('\n');
for (int i = 0; i < countof(futexes); ++i)
waiters[i].val = futexes[i];
}
}
π SEE ALSO π
futex(2), FUTEX_WAIT(2const), FUTEX_WAKE(2const), futex(7)
Kernel source file Documentation/userspace-api/futex2.rst
π and that's the whole page!! this one came from Linux man-pages 6.20, dated 2026-08-22, and it's all about futex_waitv(2) β go forth and wait on multiple futexes responsibly, bestie π¦β¨πΈ
π¦ where this page comes from
- arch rolling: man-pages 6.20-1 Β· BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
- nixpkgs unstable: man-pages 6.20
- π the page's license: MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms