Что такое поток (thread) и как Go работает с многопоточностью?

Ответ

Поток (thread) — это наименьшая единица выполнения кода, которой операционная система может управлять независимо. Каждый процесс имеет хотя бы один поток.

В Go для конкурентного (одновременного) выполнения задач используются горутины (goroutines), а не напрямую потоки операционной системы (ОС).

Ключевые отличия горутин от потоков ОС

  • Легковесность: Горутина изначально занимает всего ~2 КБ памяти в стеке, который может расти по мере необходимости. Стек потока ОС гораздо больше (1-8 МБ) и имеет фиксированный размер.
  • Управление рантаймом: Горутины управляются планировщиком Go, а не ядром ОС. Планировщик эффективно распределяет (мультиплексирует) множество горутин (M) по небольшому количеству потоков ОС (N). Это называется моделью M:N.
  • Скорость создания: Создание горутины значительно быстрее, чем создание потока ОС, так как не требует дорогостоящего системного вызова.

Пример использования

Для запуска горутины используется ключевое слово go перед вызовом функции. Для корректного ожидания завершения горутины (вместо time.Sleep) лучше использовать примитивы синхронизации, например, sync.WaitGroup.

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1) // Сообщаем, что будем ждать одну горутину

    go func() {
        defer wg.Done() // По завершении функции уменьшаем счетчик WaitGroup
        fmt.Println("Привет из горутины!")
    }()

    wg.Wait() // Ожидаем, пока счетчик WaitGroup не станет равен нулю
    fmt.Println("Горутина завершила работу.")
}

Связь с потоками ОС

Несмотря на использование горутин, рантайм Go все равно использует потоки ОС для реального выполнения кода на ядрах процессора. Количество потоков, которые могут одновременно выполнять код Go, можно контролировать с помощью переменной окружения GOMAXPROCS или функции runtime.GOMAXPROCS().