futex is a low-level waiting and waking mechanism provided by Linux. Common mutexes keep the uncontended path in user space, entering the kernel only when blocking or waking is required. It is not a complete lock, nor is it the sole implementation path for all languages, platforms, or synchronization primitives.
When designing a synchronization protocol, you must answer separately: who owns the resource, how is data published, who needs to sleep, and who should be woken up after a state change. futex primarily addresses the latter two items and their connection with shared state.
Why You Cannot Simply Check Before Sleeping
Suppose W checks ready as 0 and is about to sleep; P subsequently changes ready to 1 and wakes, but W has not yet entered the wait queue. If W then unconditionally sleeps, and no further notification occurs, it will miss the change that has already happened.
FUTEX_WAIT reads the futex word, compares it with expected, and enters a wait, forming a "compare-and-block" operation that provides atomic guarantees against concurrent operations. If the value is already unequal, it returns EAGAIN and does not sleep. FUTEX_WAIT
This requires the publisher to modify the state first, then wake up according to the protocol. Calling WAKE alone without leaving an observable state cannot reliably notify a thread that has not yet started waiting. If values repeatedly change back to the same value, generations must be distinguished using algebra, sequence numbers, or more complete protocols; one cannot assume that a single comparison eliminates all ABA problems.
Two Basic Operations
Operation
Meaning
What to do after return
FUTEX_WAIT(word, expected)
Sleep only if the value still matches
Re-check the condition or contend for the lock
FUTEX_WAKE(word, n)
Wake up to n matching waiters
The publisher must still adhere to the shared state protocol
The traditional futex word is a 32-bit value aligned to 4 bytes, even on 64-bit platforms. Ordinary WAIT does not modify the lock state for the user, WAKE does not deliver lock ownership, and there is no universal FIFO fairness guarantee. futex(2)
WAIT returning 0 only indicates that this wait was woken up; it does not allow direct entry into the critical section. EAGAIN indicates that the state changed during comparison, EINTR indicates interruption by a signal, and a timeout does not necessarily mean the business condition is false; all these paths require handling by the synchronization protocol.
The timeout for FUTEX_WAIT is relative time, while other operations may use absolute deadlines. If retrying a relative wait always resets the full original duration, the total timeout will unexpectedly extend; instead, calculate the remaining amount based on the same deadline, or use an appropriate absolute time interface.
How can WAIT avoid sleeping after a missed wake?
Publish then WAKE: zero threads may wake, but a later WAIT sees word=1 and returns EAGAIN. WAIT first, then publish and WAKE, removes the waiter. WAKE without publishing can return the waiter while word stays zero: waking and satisfying the condition differ.
Preparing the visual
How can WAIT avoid sleeping after a missed wake?
WAKE stores no future notification. WAIT compares the current word before queuing; wakeup still requires a condition recheck.
{"id":"futex-compare-and-block","title":"How can WAIT avoid sleeping after a missed wake?","summary":"WAKE stores no future notification. WAIT compares the current word before queuing; wakeup still requires a condition recheck.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"publish\" data-i18n=\"publish\"></button><button id=\"wake\" data-i18n=\"wake\"></button><button id=\"wait\" data-i18n=\"wait\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><div class=\"track-row\"><span data-i18n=\"queue\"></span><div id=\"queue\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"word\"></span><strong id=\"word\"></strong></div><div><span data-i18n=\"returned\"></span><strong id=\"returned\"></strong></div><div><span data-i18n=\"woken\"></span><strong id=\"woken\"></strong></div></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.metrics strong{font-size:17px}#returned{min-height:4em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let word=0,waiting=false,result='none',woken=null;function draw(){set('word',word);set('returned',t(result));set('woken',woken??'—');cells('queue',[{text:waiting?'W':'∅',cls:waiting?'on':'empty'}]);$('#wait').disabled=waiting;$('#publish').disabled=word===1;$('#queue').dataset.waiting=waiting;}$('#publish').onclick=()=>{word=1;draw()};$('#wake').onclick=()=>{woken=waiting?1:0;if(waiting){waiting=false;result='check'}draw()};$('#wait').onclick=()=>{if(word===0){waiting=true;result='sleep'}else result='again';draw()};$('#reset').onclick=()=>{word=0;waiting=false;result='none';woken=null;draw()};draw();","audio":false,"strings":{"again":"EAGAIN: did not sleep","check":"0: wake, then recheck","heading":"How can WAIT avoid sleeping after a missed wake?","intro":"Independently publish, WAKE and WAIT(expected=0) in any order.","none":"Not called","publish":"Publish word=1","queue":"Wait queue","reset":"Reset","returned":"Last WAIT result","scope":"One publisher and waiter; word only changes 0→1. No timeout or signals; not a complete mutex, and payload publication still requires memory ordering.","scopeLabel":"Model scope and assumptions","sleep":"Waiting","wait":"WAIT(expected=0)","wake":"WAKE(1)","woken":"Last WAKE count","word":"Shared word"}}
A Three-State Ordinary Mutex Model
The following is pseudocode to explain the algorithm, not the internal layout of pthread_mutex_t or a drop-in replacement for libc. State 0 indicates idle, 1 indicates locked with no waiter flag, and 2 indicates locked with potential waiters.
lock:
If CAS_acquire(state, 0, 1) succeeds: return
Loop:
old = exchange_acquire(state, 2)
If old == 0: return
WAIT(state, expected=2)
Handle errors based on return value, then re-contend
unlock:
old = exchange_release(state, 0)
If old == 2: WAKE(state, 1)
Marking the state as 2 informs the releaser that a wake-up might be needed, not that it precisely counts the number of waiters. A woken thread that successfully changes the state from 0 to 2 retains the flag, allowing subsequent unlocks to continue pushing other waiters. Ordinary new threads may steal the lock before the woken thread, so this model does not guarantee fairness.
If the release happens before WAIT, the mismatch prevents missed sleeps; if the wait enters the queue first, the subsequent WAKE wakes it up. The algorithm also needs to handle address lifetime, cancellation, timeouts, abnormal exits, and edge-case errors; this is why applications typically use mature pthreads or language standard library locks.
Woken threads may still lose to newly arrived threads
The releaser first writes state to 0, then wakes W; while W is still waiting for scheduling, a new thread N successfully CAS(0, 1). When W runs and sees state=1, it must re-contend and cannot enter the critical section directly just because WAIT returned 0.
Sequence
state
Who owns the lock
P still in critical section, W waiting
2
P
P releases and wakes W
0
Temporarily no one
N wins CAS first
1
N
W resumes, exchange becomes 2 getting old value 1
2
Still N
N releases, sees old value 2, continues waking
0
Wait for next contention
The value of state 2 lies in preserving the information that "subsequent releases may need notification." It is not proof that W owns the lock, nor is it the current number of waiters. This scheduling explains why ordinary futex locks may have good throughput but cannot directly promise waiting fairness or worst-case waiting time.
WAIT does not sleep when comparison mismatches
The following only verifies the comparison and return of the single-threaded futex interface, not implementing a concurrent lock. The word is already 1, yet it requests waiting for value 0; even if the timeout pointer is NULL, it should immediately return EAGAIN.
#define_GNU_SOURCE#include<errno.h>#include<linux/futex.h>#include<stdint.h>#include<stdio.h>#include<sys/syscall.h>#include<unistd.h>intmain(void){_Alignas(4)uint32_t word =1;
errno =0;long result =syscall(SYS_futex,&word, FUTEX_WAIT_PRIVATE,0,NULL,NULL,0);int saved = errno;printf("mismatch=%d\n", result ==-1&& saved == EAGAIN);return!(result ==-1&& saved == EAGAIN);}
Save as futex-mismatch.c, compile and run expecting mismatch=1. This example uses libc syscall, so it sees -1 and errno; it does not concurrently modify the ordinary uint32_t. When extending it to a multi-threaded protocol, you must re-satisfy the atomic object and memory ordering constraints below. WAIT comparison semantics
Memory Ordering for Atomic Variables Cannot Be Omitted
Data protected by locks usually establishes a synchronization relationship between the release in unlock and the acquire in successful lock. The queue atomicity of futex cannot replace the data publication protocol of the language memory model, nor can it automatically turn concurrent reads and writes of ordinary shared integers into legal atomic operations.
volatile only affects specific compiler access semantics and cannot replace cross-thread atomicity and acquire/release. If directly implementing a futex wrapper, you must also confirm the size, alignment, lock-free support of the atomic object, and compatibility of the target ABI with the kernel interface.
The PRIVATE flag indicates that the waiting parties use the object in the same address space, allowing kernel lookup optimization. Inter-process shared objects must use operations suitable for shared mappings; PRIVATE cannot be treated as an unconditionally faster option. Before destroying, unmapping, or reusing a futex address, you must also ensure that no old operations still reference it.
PI and Robust Solve Different Problems
Priority Inheritance (PI) addresses the situation where a low-priority lock holder is constantly preempted by a medium-priority task, causing a high-priority waiter to be unable to proceed. The kernel can elevate the effective priority of the lock holder along the wait chain, helping it complete the critical section. PI requires specific owner encoding and operation protocols; it cannot be casually implemented using ordinary WAIT/WAKE.
The pthread interface selects PTHREAD_PRIO_INHERIT via mutex attributes. It does not solve deadlocks, does not shorten the critical section itself, and does not guarantee meeting deadlines under arbitrary scheduling conditions.
Robust mutexes, on the other hand, handle the death of the lock-holding thread: the next pthread_mutex_lock may return EOWNERDEAD, at which point the caller already holds the lock, but the protected data may be inconsistent. The application should fix the data and call pthread_mutex_consistent, then unlock. If unlocked without restoring consistency, subsequent operations may receive ENOTRECOVERABLE.
You cannot directly treat EOWNERDEAD as "lock acquisition failed, did not get the lock," nor can you uniformly mark everything as consistent without checking the data. The kernel provides the abnormal owner state and does not automatically roll back business modifications. Robust mutex
Complete Recovery Example: Recovering Uncommitted Updates with a Committed Copy
The following creates only one worker thread within the process. The normal invariant is working == committed; the worker thread holds the lock, changes working to 99, and then exits directly, simulating an uncommitted update. The recovery party chooses to "discard uncommitted content," restoring working to committed=7, then marking it consistent.
#define_POSIX_C_SOURCE200809L#include<errno.h>#include<pthread.h>#include<stdio.h>#include<stdlib.h>staticpthread_mutex_t mutex;staticint committed =7, working =7;staticvoidrequire(intresult){if(result !=0)abort();}staticvoid*unfinished(void*unused){(void)unused;require(pthread_mutex_lock(&mutex));
working =99;returnNULL;/* Intentionally exit thread holding robust mutex. */}intmain(void){pthread_mutexattr_t attr;require(pthread_mutexattr_init(&attr));require(pthread_mutexattr_setrobust(&attr, PTHREAD_MUTEX_ROBUST));require(pthread_mutex_init(&mutex,&attr));require(pthread_mutexattr_destroy(&attr));pthread_t thread;require(pthread_create(&thread,NULL, unfinished,NULL));require(pthread_join(thread,NULL));int result =pthread_mutex_lock(&mutex);if(result != EOWNERDEAD)abort();if(working !=99|| committed !=7)abort();
working = committed;require(pthread_mutex_consistent(&mutex));require(pthread_mutex_unlock(&mutex));require(pthread_mutex_lock(&mutex));int recovered = working ==7&& working == committed;require(pthread_mutex_unlock(&mutex));require(pthread_mutex_destroy(&mutex));printf("recovered=%d\n", recovered);return!recovered;}
Save as robust-demo.c, compile with cc -Wall -Wextra -pthread robust-demo.c -o robust-demo. Expect recovered=1, and the next lock after recovery should return 0 normally. Robust mutex recovery protocol
In this example, the committed copy was not modified by the abnormal thread, so the recovery rule is clear. Complex objects may also involve partial writes, linked list structures, and external side effects; if committed itself might be incomplete, simple assignment does not constitute valid recovery. You should first define verifiable invariants and recovery data, then decide when to call consistent.
EOWNERDEAD: lock acquired, data not repaired
After owner death, lock yields both EOWNERDEAD and recovery-thread ownership. Unlock directly to make the mutex unrecoverable; restore working, declare consistency and unlock to allow a normal later lock. Data repair and consistency declaration are distinct required steps.
Preparing the visual
EOWNERDEAD: lock acquired, data not repaired
EOWNERDEAD grants the lock with potentially inconsistent data. Restore the invariant and declare consistency; unlocking without that can leave ENOTRECOVERABLE.
Operations like FUTEX_REQUEUE can move wait queues, reducing thundering herd effects in certain condition variable patterns. FUTEX_WAKE_OP combines an atomic operation on a second word with conditional waking; it is not "atomic add followed by WAIT," FUTEX_OP is a macro encoding the operation. FUTEX_WAKE_OP
futex_waitv accepts a set of words to compare and waits for any one of them to be woken, suitable for multiple wait sources. After returning an index, you still need to check the business state; it does not represent acquiring multiple locks simultaneously. This interface uses absolute timeouts and specified clocks.
"futex2" involved many extension ideas; you cannot write all the widths, NUMA, and scheduling features in the proposal as facts already possessed by all 6.x kernels. The kernel futex2 documentation checked in this article explicitly requires FUTEX_32 for waitv, although the structure fields reserve space for extensions. Kernel futex2 documentation
How to Understand Performance
The uncontended path may have no system calls, but atomic read-modify-write still brings cache coherence overhead. When contending, it may also include spinning, queue operations, context switches, scheduling delays, and cross-NUMA data transmission. There is no universal conclusion of a fixed "20 ns" or exactly one WAIT plus one WAKE per contention.
When observing slow locks, distinguish between hold time, wait time, contention frequency, and scheduling impact. Tracing methods from function entry to return can be found in ftrace and bpftrace.
Keywords: futex, compare and block, lost wakeup, FUTEX_WAIT, FUTEX_WAKE, acquire release, PI, robust mutex, EOWNERDEAD, futex_waitv