Before the current runtime work-stealing scheduler was implemented, Go's goroutine scheduler was simpler. "Ready-to-run" goroutines were matched with "waiting-for-work" machines. When there were ready goroutines and no waiting machines, a new machine was started running in a new OS thread. All ready goroutines could run simultaneously, up to a limit.
Unfortunately this meant it had a limited scalability of concurrent programs; particularly high throughput servers and parallel computational programs.
The biggest change was to introduce processors into the runtime and implement a work-stealing scheduler on top of them.
So now we have three main actors for Go's scheduler:
-
M: Machine or worker thread
-
G: Goroutine
-
P: Processor, a resource that is required to execute Go code.
M must have an associated P to execute Go code, however it can be blocked or in a syscall w/o an associated P.