Livelock and starvation

1 min read

A livelock is when threads are not blocked, but keep reacting to each other without making progress. Two threads that each release a lock when they cannot get the second, then retry at the same moment, can repeat forever. A random back-off breaks the pattern. Unlike a deadlock, a livelock uses CPU.

Starvation

A thread is starved when it is ready to run but another thread keeps getting the resource first, such as a lock that busy threads always win, or a low-priority thread that higher-priority threads always preempt. Priority inversion is a case of it: a high-priority thread waits for a lock held by a low-priority thread, so it effectively runs at low priority.

Thread pool starvation

In .NET, starvation of the thread pool happens when blocking calls hold pool threads while the pool adds new ones slowly (see the thread poolThe thread poolA set of reusable worker threads that take jobs from a queue.Opens in a new tab). Typical causes are .Result and .Wait(), Thread.Sleep, synchronous I/O, and lock contention. The sign is slow responses while CPU stays low, with the pool’s queue length growing, which dotnet-counters shows.

Where it matters

A deadlock stops threads and uses no CPU. A livelock keeps them busy and uses CPU. Starvation leaves some threads waiting while others progress. The first is in deadlocksDeadlocksTwo or more threads wait on each other forever.Opens in a new tab. Avoiding pool starvation means not blocking pool threads, as in Thread.Sleep versus Task.DelayThread.Sleep versus Task.DelaySleep blocks the thread. Delay frees it.Opens in a new tab.


Sources