Go

How does work stealing work, and in what order does a P look for work?

Question 150HardGo 1.22 to 1.25

When the current goroutine blocks or gives up the CPU, the P calls schedule() → findRunnable(). It checks these sources, roughly in order:

  1. Every 61st scheduling tick, the global run queue first, so that goroutines there are not starved.
  2. The runnext slot, then the local run queue, a lock-free ring of 256 slots.
  3. The global run queue, taking a batch of goroutines.
  4. A non-blocking netpoll, which picks up goroutines whose I/O is ready.
  5. Stealing: it visits other Ps in random order and takes half of a victim's local queue. It can also steal the victim's timers and, as a last resort, its runnext.
  6. Only then does the M release its P, park, and possibly block in netpoll.

If a local queue overflows, half of it moves to the global queue. Spinning Ms are threads that are looking for work. The runtime limits them to about GOMAXPROCS/2 so they do not waste CPU, but keeps at least one spinning when there is new work, so runnable goroutines get picked up quickly.

Gotcha: goroutines do not run in FIFO order. The runnext slot gives LIFO behaviour for the most recently created goroutine.

More on Goroutines & the Scheduler

All 35 Goroutines & the Scheduler questions