Middle+
What is runtime in Go and how does the scheduler work?
sobes.tech AI
Answer from AI
Runtime in Go is the execution environment that includes:
- Scheduler: manages goroutines and their execution on system threads.
- Garbage collector: automatically manages memory.
- System calls: wrappers over OS system calls.
- Network I/O: non-blocking network interaction.
The Go scheduler implements an M:N model, where M goroutines are executed on N system threads. It uses a three-component G-P-M model:
- G (Goroutine): lightweight thread of execution managed by the Go scheduler.
- P (Processor): logical processor representing the context needed to run goroutines (goroutine queue, stack cache). The default number of P is
runtime.NumCPU(). - M (Machine): system thread on which code runs.
Scheduler working principle:
- Creating goroutines: New goroutines are created using the
gokeyword. - Queuing: The created goroutine is placed in a local P queue or a global queue if the local one is full.
- Execution: M is bound to P and selects a goroutine from its local queue (or global if local is empty). M executes the goroutine's code.
- Context switching: Occurs when a goroutine blocks (e.g., on a system call, waiting for a mutex, I/O operation) or when the scheduler decides to move a goroutine (e.g., after a time slice).
- Blocking M: If a goroutine makes a blocking system call, the current M is detached from P, and the scheduler finds or creates a new M for that P. The blocked M is returned to the pool when the system call completes.
- Preemption: In older Go versions, preemption was cooperative (goroutine had to explicitly yield). Modern versions (from 1.14) implement asynchronous preemption based on OS signals, allowing interruption of goroutines stuck in non-blocking code.
- Work stealing: If a P runs out of goroutines in its local queue, it can "steal" goroutines from other P's or from the global queue.
Advantages of the Go scheduler:
- High performance: efficiently utilizes multi-core processors.
- Lightweight goroutines: allows creating millions of concurrently running "threads".
- Non-blocking I/O: simplifies writing high-performance network applications.
- Ease of use: developers do not need to manage threads manually.
Example of creating a goroutine:
package main
import (
"fmt"
"time"
)
func worker() {
fmt.Println("Goroutine is running")
}
func main() {
go worker() // Creating a new goroutine
time.Sleep(time.Second) // Waiting for the goroutine to finish
fmt.Println("Main goroutine finished")
}