When g
yields it’s enqueued to global schedt.runq
because if we yield we are not highly prioritized (globrunqput
)
When g
readies another g'
, g'
is enqueued to local p.runq
without locking (runqput
) (see Fairness)
If local p.runq
is full, we enqueue batch (half of g
s) to global schedt.runq
, because it means that we have to much work on our p
. Note that in that case we need to hold the lock schedt.lock
for global schedt.runq
(contended slow case)
Function schedule
schedules a goroutine. It calls findRunnable
to find next g
to run:
- Call
runqget
to getg
fromp.runq
and run it - Call
globrunqget
. It getsg
from globalschedt.runq
acquiring the lockschedt.lock
(contended slow case) and also moves the batch ofg
s fromschedt.runq
top.runq
to minimize the # of accesses toschedt.runq
- Poll network
- Steal batch (half of
g
s) from others (random)p
s by callingrunqsteal
(work stealing) - Sleep
Local p.runq
have a single producer but multiple consumers, because there can be multiple p
s that steal work from our p.runq
. Thus, when enqueueing to p.runq
we can use atomic store but when dequeueing we need to use spinlocks (CAS)
Because findRunnable
would starve schedt.runq
if p.runq
is always non empty, schedule
tries to dequeue one g
from schedt.runq
in iteration of scheduling