← Назад к вопросам

Как работает многопоточность в Go?

2.0 Middle🔥 241 комментариев
#Конкурентность и горутины#Производительность и оптимизация

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает многопоточность в Go?

В Go многопоточность реализована через горутины — легковесные потоки, управляемые средой выполнения Go, а не операционной системой. Это ключевое отличие от классических потоков ОС делает параллельное программирование более эффективным и простым.

Основные компоненты многопоточности в Go

  1. Горутины
    Горутина — это функция или метод, выполняющийся конкурентно с другими горутинами в том же адресном пространстве. Они создаются с помощью ключевого слова go. Одна программа Go может запускать тысячи горутин одновременно.

    go func() {
        fmt.Println("Горутина работает!")
    }()
    
  2. Планировщик Go
    Планировщик Go (scheduler) отвечает за распределение горутин по потокам ОС. Он работает по модели M:N, где M горутин планируются на N потоков ОС. Планировщик использует технику work-stealing для балансировки нагрузки: незагруженные потоки "крадут" задачи у перегруженных.

  3. Каналы
    Каналы (channels) — это типизированные конвейеры для безопасной коммуникации между горутинами. Они предотвращают race conditions без явных блокировок.

    ch := make(chan int)
    go func() {
        ch <- 42 // отправка данных
    }()
    value := <-ch // получение данных
    
  4. Примитивы синхронизации
    В пакете sync предоставляются мьютексы (sync.Mutex), wait группы (sync.WaitGroup) и другие инструменты для координации горутин.

Как это работает на практике

Создание горутин требует всего 2 КБ стека (динамически растущего), тогда как потоки ОС обычно резервируют мегабайты. Это позволяет создавать миллионы горутин.

Планирование происходит кооперативно — горутина добровольно уступает контроль в точках:

  • При операциях ввода/вывода
  • При вызове runtime.Gosched()
  • При операциях с каналами
  • В начале сборки мусора

Пример паттерна worker pool:

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // Создаем пул воркеров
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // Отправляем задачи
    for j := 1; j <= 9; j++ {
        jobs <- j
    }
    close(jobs)
    
    // Собираем результаты
    for r := 1; r <= 9; r++ {
        <-results
    }
}

Преимущества подхода Go

  • Эффективность: Горутины дешевле потоков ОС в 100+ раз по памяти
  • Простота: Синтаксис go func() и каналы интуитивно понятны
  • Безопасность: Каналы поощряют CSP-модель (Communicating Sequential Processes), уменьшая потребность в мьютексах
  • Масштабируемость: Планировщик автоматически распределяет нагрузку по ядрам CPU

Отличия от других языков

В отличие от Java/C#, где потоками управляет ОС, или Python/JavaScript с GIL, Go предлагает настоящую многопоточность без глобальной блокировки. Планировщик Go работает в пространстве пользователя, минимизируя дорогостоящие системные вызовы.

Важное ограничение: Горутины не имеют идентификаторов, их нельзя принудительно завершить — это сознательное дизайнерское решение для предотвращения race conditions.

Многопоточность в Go — это элегантный компромисс между производительностью системного уровня и простотой высокоуровневых абстракций, что объясняет популярность Go в облачных и распределенных системах.