Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

ℹ️ This article is based on Go 1.13.

All the goroutines created in Go are under the management of an internal scheduler. The Go scheduler tries to give running time to all goroutines and keep all CPU busy with running goroutines when the current ones are blocked or terminated. It actually runs as a special goroutine.

Scheduling goroutine

Go limits the number of OS thread running thanks to GOMAXPROCS variable simultaneously. That means Go has to schedule and manage goroutines on each of the running threads. This role is delegated to a special goroutine, called g0 , that is the first goroutine created for each OS thread:

Then, it will schedule ready goroutines to run on the threads.

For more information about the P , M , G model, I suggest you read my article “Go: Goroutine, OS Thread and CPU Management.”

To better understand how scheduling works on g0 , let’s review the usage of the channels. Here is when a goroutine blocks on sending on channels:

ch := make(chan int)

[...]

ch <- v

When blocking on channels, the current goroutine will be parked, i.e., be in waiting mode and not been pushed in any goroutines queues:

Then, g0 replaces the goroutine and does on round one scheduling:

The local queue has the priority during the scheduling, and the goroutine #2 will now run:

For more information about scheduling priority, I suggest you read my article “Go: Work-Stealing in Go Scheduler.”

The goroutine #7 will get unblocked as soon as a receiver comes with reading the channel:

v := <-ch

The goroutine receiving the message will switch to g0 and unlock the parked goroutine by putting it on the local queue:

Although the special goroutine is managing the scheduling, it is not its only job, it does much more.

Responsibilities

Contrary to the regular goroutines, g0 has a fix and larger stack. This allows Go to perform operations where a bigger stack is needed, and when it is preferable for the stack not to grow. Among the responsibilities of g0 , we can list:

Goroutine creation. When calling go func(){ ... }() or go myFunction() , Go will delegate the function creation to g0 before putting it on the local queue:

Newly created goroutines run in priority and are placed at the top of the local queue.

For more information about the prioritization of the goroutines, I suggest you read my article “Go: Concurrency & Scheduler Affinity.”

Defer functions allocations.

Garbage collector operations, such as stopping the world, scanning the stack of the goroutines, and some of the marking and sweeping operations.

Stack growth. When needed, Go increases the size of the goroutines. This operation is done by g0 in the prolog functions.