Middle
Tell about goroutine scheduling in Go.
sobes.tech AI
Answer from AI
The Go scheduler uses a multi-threaded M:N model, where M goroutines are mapped onto N OS threads. The scheduler's work is based on the GMP model (Goroutine, Machine, P).
- G (Goroutine): A lightweight thread of execution. Created with the
gokeyword. - M (Machine): An OS thread. Executes goroutine code. A hot thread, with blocking system calls executed on a separate M.
- P (Processor): A local queue of goroutines to which an M is bound. The number of P defaults to the number of logical CPU cores and is controlled by the
GOMAXPROCSvariable.
Working principle:
- When a goroutine needs to be run, it is placed in the local P queue.
- The M bound to P takes a goroutine from the queue and starts executing it.
- If the local P queue is empty, M can "steal" a goroutine from another P's queue.
- If a goroutine blocks (e.g., during a system call), the current M detaches from P, and P is bound to another available M or a new M is created. The blocked goroutine remains on the blocked M until the blocking operation completes.
- When the blocking operation finishes, the goroutine is returned to another P's queue or the current P if it is free.
- The scheduler periodically (every 10 ms) checks goroutines for execution longer than 10 ms for preemption.
Advantages of this approach:
- Low overhead for creating and switching goroutines (much less than OS threads).
- Efficient CPU resource utilization through load balancing among P.
- Asymptotically better performance compared to the 1:1 model (each goroutine is an OS thread).
Scheduling models:
- Runqueue: Each P has a local queue of ready-to-run goroutines.
- Global Runqueue: A global queue for "stolen" or ready-to-run goroutines when local queues are empty. Used less frequently.
The Go scheduler is not strictly preemptive. Context switching occurs:
- During blocking operations (I/O, mutexes, channels).
- When calling functions that may lead to a context switch (e.g., runtime calls).
- During "voluntary yield" via
runtime.Gosched(). - During time-based preemption (scheduled from Go 1.14).
// Example of using goroutines
package main
import (
"fmt"
"time"
)
func worker(id int) {
fmt.Printf("Worker %d started\n", id)
time.Sleep(time.Second) // Simulate work
fmt.Printf("Worker %d finished\n", id)
}
func main() {
for i := 1; i <= 5; i++ {
go worker(i) // Create and start goroutine
}
time.Sleep(2 * time.Second) // Wait for all goroutines to finish
fmt.Println("All workers finished")
}