Middle+
Rääkige Go-s goroutine'ide planeerimisest.
sobes.tech AI
Vastus AI-lt
The goroutine scheduler in Go uses an M:N model, where M goroutines are mapped onto N OS threads. It efficiently switches goroutines between threads, implementing preemption points with stop points.
Main components of the GPM model:
- G (Goroutine): Abstraction of an executable code unit, lightweight thread managed by the Go scheduler.
- P (Processor): Logical processor representing the context of a local goroutine queue and resources such as mutexes and the scheduler. The number of P is by default determined by the number of logical CPU cores, but can be changed via
GOMAXPROCS. - M (Machine/Thread): An OS thread that executes code. M is associated with P and executes goroutines from the local P queue or the global queue.
Working principle:
- A new goroutine is created and placed into the available P's local queue.
- The M associated with P takes a goroutine from the local queue and executes it.
- If the local queue is empty, M tries to steal goroutines from other P's.
- If P blocks (e.g., during a system call), its associated M disconnects, and another M connects to this P. The blocked M can be reused once the system call completes.
- If a goroutine blocks on a channel or mutex, it is queued, and the scheduler selects another goroutine to run on the same M.
- The scheduler periodically checks for long-running goroutines to preempt, allowing other goroutines to get execution time.
Preemption points occur at certain places:
- Function/method calls.
- Loops containing function calls.
- Memory allocation.
- Synchronization primitives (channels, mutexes).
Example scheduling:
package main
import (
"fmt"
"runtime"
"time"
)
func worker(id int) {
fmt.Printf("Goroutine %d started work\n", id)
time.Sleep(time.Second) // Simulate work
fmt.Printf("Goroutine %d finished work\n", id)
}
func main() {
runtime.GOMAXPROCS(2) // Set number of logical processors
for i := 1; i <= 5; i++ {
go worker(i) // Create goroutines
}
time.Sleep(3 * time.Second) // Wait for goroutines to finish
fmt.Println("Main goroutine finished")
}
In this example, 5 goroutines are scheduled on 2 logical processors. The Go scheduler distributes these goroutines among available OS threads, using local and possibly global queues, as well as a work stealing mechanism.
Advantages of this approach:
- Scalability: Efficient utilization of multi-core systems.
- Low overhead: Lightweight goroutines compared to OS threads.
- Ease of use: Built-in scheduling makes parallel programming more accessible.