Skip to main content

Priorities, Nice Values, and Priority Inversion

What This Concept Is

Priority says "some processes matter more than others." Unix expresses this through:

  • nice value: integer in [-20, 19] on Linux. Lower is higher priority (less nice to others). Default 0. Changed via nice(+5) or renice.
  • static priority: kernel-internal field derived from nice.
  • real-time priorities (separate class): SCHED_FIFO and SCHED_RR in [1, 99], always above any normal task.

Priority inversion is the pathology where a high-priority task is indirectly blocked by a low-priority task, typically because:

  1. Low-priority task L holds a lock.
  2. High-priority task H becomes runnable, tries to acquire the same lock, blocks.
  3. Medium-priority task M runs (no lock needed), preempting L.
  4. L cannot release the lock, so H waits -- on a medium-priority job.

H is effectively held hostage by M, even though H should dominate M.

Why It Matters Here

Priorities seem harmless ("just set it higher"). They are not. Priority inversion has caused real failures -- most famously Mars Pathfinder in 1997, where a low-priority data-gathering task held a mutex needed by a high-priority bus task, a medium-priority task kept preempting the low one, and the watchdog reset the spacecraft repeatedly. The fix was enabling priority inheritance on the mutex.

Concrete Example

Three tasks, one mutex, Linux-like scheduling:

 t=0   L (low, nice +10)  acquires mutex M
t=5 H (high, nice -10) becomes runnable, tries to acquire M -> blocks
t=6 M (medium, nice 0) becomes runnable, preempts L
... M runs for 500 ms doing unrelated CPU work
t=506 M finishes; L resumes; releases mutex; H finally runs

H saw a 501 ms latency on a task that should have taken microseconds. The bug is invisible in CPU utilization metrics: CPU was always busy. It shows up only in H's observed latency.

Common Confusion / Misconception

"Raise the priority of H to make it faster." Useless: H already has the highest priority and is still blocked on the lock.

"Never use priorities." Also wrong. Audio pipelines, video encoding, networking fast-path threads, and real-time control all depend on priorities being respected. The fix is not to abandon them; it is to enable priority inheritance on any lock that crosses priority boundaries.

Priority inheritance: while L holds a lock H is waiting for, L temporarily inherits H's priority. So L preempts M, finishes its critical section, drops back to its own priority, and H proceeds.

How To Use It

When you design a system that mixes priorities:

  1. List every lock (mutex, spinlock, rwlock) in the code path of a high-priority task.
  2. For each lock, list every task that ever holds it.
  3. If any of those holders can be lower priority than the high-priority waiter, you have a potential inversion.
  4. Turn on priority inheritance (PTHREAD_PRIO_INHERIT on pthread mutexes, PI-futex under the hood on Linux) or redesign so that the fast path does not take the shared lock.

Check Yourself

  1. Why does raising H's priority not fix priority inversion?
  2. What does priority inheritance change about L's behavior while holding the lock?
  3. Why does the inversion not show up in CPU utilization graphs?

Mini Drill or Application

For each scenario, say whether priority inversion is possible and how to fix it:

  1. A web server's accept thread is SCHED_FIFO, its worker threads are normal, and they all share a stats mutex.
  2. An audio callback (SCHED_FIFO) and a logger (normal) share a ring buffer lock.
  3. Two equal-priority tasks share a lock with a third, higher-priority task never touching that lock.

Read This Only If Stuck