В чем отличие горутины от потока?go-70

Горутины и потоки ОС — это принципиально разные механизмы параллельного выполнения кода. Рассмотрим их ключевые различия в контексте Go.

1. Основные различия

Характеристика Горутина Поток ОС
Реализация Уровень языка (runtime Go) Уровень ОС
Память 2-8 КБ стек (динамический) 1-2 МБ (фиксированный)
Переключение контекста 100-200 нс 1000-1500 нс
Планирование Кооперативное (с вытеснением с Go 1.14) Вытесняющее
Модель M:N (много горутин на few потоках) 1:1
Создание Дешевое (go func()) Дорогое

2. Детальное сравнение

2.1. Ресурсы и производительность

// Пример создания 10000 горутин
func main() {
    for i := 0; i < 10000; i++ {
        go func(n int) {
            fmt.Println(n)
        }(i)
    }
    time.Sleep(time.Second)
}
  • Горутины: Можно создавать сотни тысяч (начальный стек 2КБ, растет динамически)
  • Потоки: Обычно ограничение в несколько тысяч на процесс (стек 1-2МБ по умолчанию)

2.2. Планирование

  • Горутины управляются планировщиком Go (не ОС):
    • Работают на нескольких потоках ОС (GOMAXPROCS)
    • Переключение происходит при:
      • Вызове runtime.Gosched()
      • Блокирующих операциях (каналы, syscalls)
      • Через ```10мс времени выполнения (с Go 1.14)
// Пример переключения горутин
func worker() {
    for {
        runtime.Gosched() // Явное переключение
    }
}
  • Потоки управляются ОС:
    • Вытесняющее многозадачность
    • Переключение по кванту времени (```10-20мс)

2.3. Синхронизация

Горутины используют более легковесные механизмы:

  • Каналы (channels)
  • sync.Mutex (оптимизирован для горутин)
  • Атомарные операции
  • select для мультиплексирования
// Пример синхронизации горутин
ch := make(chan int)
go func() { ch <- 42 }()
value := <-ch

3. Реализация в Go runtime

Горутины работают в модели M:N:

  • M - потоки машины (ОС)
  • N - горутины
  • P - процессоры (логические, GOMAXPROCS)
[Горутины (G)] ---исполняются на---> [Процессоры (P)] ---мапятся на---> [Потоки ОС (M)]

4. Когда что использовать

Горутины идеальны для:

  • Высоконагруженных серверов
  • Конкурентных задач (не CPU-bound)
  • Работы с каналами
  • Асинхронного IO

Потоки нужны для:

  • Системных вызовов (Go runtime использует под капотом)
  • CPU-bound задач (но лучше использовать горутины с GOMAXPROCS)
  • Интеграции с C-библиотеками (cgo)

5. Практические примеры

5.1. Сетевой сервер на горутинах

func handleConn(conn net.Conn) {
    defer conn.Close()
    // Обработка соединения
}

func main() {
    ln, _ := net.Listen("tcp", ":8080")
    for {
        conn, _ := ln.Accept()
        go handleConn(conn) // Новая горутина на соединение
    }
}

5.2. Синхронизация горутин

var wg sync.WaitGroup

func worker(id int) {
    defer wg.Done()
    fmt.Printf("Worker %d starting\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Worker %d done\n", id)
}

func main() {
    for i := 1; i <= 5; i++ {
        wg.Add(1)
        go worker(i)
    }
    wg.Wait()
}

Резюмируем

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

  1. Легковесность: Горутины потребляют меньше памяти и быстрее создаются
  2. Эффективность: Быстрое переключение контекста в userspace
  3. Интеграция: Глубоко встроены в язык с удобными примитивами синхронизации
  4. Масштабируемость: Позволяют создавать сотни тысяч параллельных задач

Горутины — это мощная абстракция Go для конкурентности, которая использует потоки ОС под капотом, но предоставляет более удобный и эффективный интерфейс.