the helping technique for wait-freedom
There is a tension at the heart of wait-freedom. Lock-free algorithms let a thread retry forever, so any single thread might never finish. To guarantee that EVERY thread finishes in bounded steps, you need a way for a thread that keeps losing races to nonetheless get its operation done. The helping technique is the answer: instead of only doing your own work and hoping to win, you finish other threads' work when you find it half-done, so that nobody is ever left behind.
Here is how it is built. A thread announces its intended operation in a shared place — typically by publishing a descriptor, a small record that fully describes what it wants to do (which slot, what value, what step it is on). Now any other thread that comes along and sees an in-progress descriptor does not just step around it; it reads the descriptor, performs the remaining steps of that operation on the owner's behalf, and marks it complete. Because the descriptor captures the whole operation, a helper can finish it correctly even though it is someone else's. The Michael-Scott queue you have seen contains a baby version of this: any thread that finds tail lagging behind advances it before doing its own enqueue. Full wait-free constructions generalise this so that within a bounded number of steps, either you complete your own operation or someone helps complete it for you.
The honest reality is that helping is what makes general wait-free algorithms possible and also what makes them expensive and intricate. Every operation must be expressed as a published descriptor that strangers can pick up and finish, which adds memory traffic and complexity to the fast path, and getting the helping protocol correct — so two helpers do not finish the same operation twice or corrupt each other — is famously delicate. This is the core reason wait-free code is reserved for the rare cases where a hard per-thread progress bound is genuinely required, rather than used everywhere.
/* A thread publishes a descriptor; any helper can finish the operation. */ struct Desc { int op; void *target; void *arg; atomic int state; }; void help(struct Desc *d) { /* run by the owner OR a passer-by */ if (d->state == PENDING) { apply(d->target, d->arg); /* do the remaining work for d */ CAS(&d->state, PENDING, DONE); } }
Because the descriptor fully describes the operation, a stranger can complete it correctly — so no thread can be starved into never finishing.
Helping is the standard route from lock-free to wait-free, but it makes every operation a publishable descriptor, adding overhead and subtle correctness pitfalls (double-completion, helper races). Use it only when a hard per-thread bound is truly needed.