Sobes.tech
Middle+

Tell about goroutine scheduling in Go.

sobes.tech AI

Answer from AI

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 preemptive multitasking with preemption points.

Main components of the GPM model:

  • G (Goroutine): An abstraction of an executable code unit, a lightweight thread managed by the Go scheduler.
  • P (Processor): A logical processor representing the context of a local goroutine queue and resources such as mutexes and the scheduler. The number of P's 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 P's local queue or the global queue.

Working principle:

  1. A new goroutine is created and placed in the local queue of an available P.
  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), the associated M detaches, and another M is linked 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 put into a wait queue, and the scheduler selects another goroutine to run on the same M.
  6. The scheduler performs periodic checks to preempt long-running goroutines, allowing others to run.

Preemption points occur at specific locations:

  • Function/method calls.
  • Loops containing function calls.
  • Memory allocation.
  • Synchronization primitives (channels, mutexes).

Example of scheduling:

package main

import (
	"fmt"
	"runtime"
	"time"
)

func worker(id int) {
	fmt.Printf("Goroutine %d started\n", id)
	time.Sleep(time.Second) // Simulate work
	fmt.Printf("Goroutine %d finished\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 will distribute 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 use of multi-core systems.
  • Low overhead: Lightweight goroutines compared to OS threads.
  • Ease of use: Built-in scheduling makes parallel programming more accessible.