Sobes.tech
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:

  1. A new goroutine is created and placed into the available P's local queue.
  2. The M associated with P takes a goroutine from the local queue and executes it.
  3. If the local queue is empty, M tries to steal goroutines from other P's.
  4. 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.
  5. If a goroutine blocks on a channel or mutex, it is queued, and the scheduler selects another goroutine to run on the same M.
  6. 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.