В чем разница между процессами, потоками и горутинами?

Ответ

Это три разных уровня абстракции для выполнения кода.

Процесс (Process)

  • Что это: Экземпляр запущенной программы. Операционная система выделяет ему изолированное адресное пространство памяти, файловые дескрипторы и другие ресурсы.
  • Изоляция: Процессы полностью изолированы друг от друга. Один процесс не может напрямую получить доступ к памяти другого.
  • Взаимодействие: Через механизмы межпроцессного взаимодействия (IPC), такие как пайпы, сокеты, общая память.
  • Стоимость: Создание и переключение контекста между процессами — очень дорогостоящая операция для ОС.

Поток (OS Thread)

  • Что это: Наименьшая единица выполнения, которой управляет планировщик операционной системы. Потоки выполняются в рамках одного процесса.
  • Изоляция: Потоки одного процесса разделяют общее адресное пространство памяти, но у каждого есть свой стек вызовов.
  • Взаимодействие: Очень простое и быстрое, так как они могут напрямую читать и писать в общую память (требует синхронизации: мьютексы, семафоры).
  • Стоимость: Создание и переключение контекста дешевле, чем у процессов, но все еще имеет значительные накладные расходы.

Горутина (Goroutine)

  • Что это: Легковесный "поток", управляемый средой выполнения Go (Go runtime), а не операционной системой.
  • Изоляция: Горутины выполняются в общем адресном пространстве, как и потоки.
  • Взаимодействие: Идиоматичный способ — через каналы (chan), что позволяет избежать проблем с состоянием гонки. Прямой доступ к общей памяти также возможен, но требует примитивов синхронизации (sync пакет).
  • Стоимость: Создание и переключение контекста чрезвычайно дешевы. Можно легко запустить сотни тысяч и даже миллионы горутин.

Ключевое отличие горутин:

Go использует модель планирования M:N, где M горутин выполняются на N потоках ОС (где M обычно гораздо больше N). Go runtime сам решает, какая горутина на каком потоке будет выполняться, эффективно мультиплексируя их. Это позволяет достичь высокой степени параллелизма при меньшем потреблении ресурсов по сравнению с традиционной моделью "один поток на задачу".

Пример с горутиной (идиоматичное ожидание завершения):

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    // WaitGroup используется для ожидания завершения всех горутин
    var wg sync.WaitGroup

    // Сообщаем WaitGroup, что мы ожидаем завершения одной горутины
    wg.Add(1)

    go func() {
        // defer гарантирует, что Done() будет вызван при выходе из функции
        defer wg.Done()
        fmt.Println("Hello from goroutine")
        time.Sleep(50 * time.Millisecond)
    }()

    fmt.Println("Hello from main")

    // Блокируем выполнение main, пока счетчик WaitGroup не станет равен нулю
    wg.Wait()
    fmt.Println("Goroutine finished, exiting")
}