Как реализована параллельность в Go?

Ответ

В Go параллельность реализована через горутины (goroutines) — легковесные потоки, управляемые рантаймом языка. Они работают поверх системных потоков, но потребляют значительно меньше ресурсов (стартовый стек ~2 КБ).

Каналы (channels) — основной способ коммуникации между горутинами. Они обеспечивают безопасную синхронизацию и передачу данных, следуя принципу "Не обменивайтесь данными путем совместного использования памяти; вместо этого обменивайтесь памятью путем коммуникации" (Don't communicate by sharing memory; share memory by communicating).

Пример использования горутин и каналов:

package main

import (
    "fmt"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Printf("Рабочий %d начал задачу %dn", id, j)
        time.Sleep(time.Millisecond * 500) // Имитация работы
        fmt.Printf("Рабочий %d закончил задачу %dn", id, j)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)

    // Запуск 3 горутин-рабочих
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // Отправка 5 задач в канал jobs
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs) // Важно закрыть канал, чтобы рабочие знали, когда задач больше нет

    // Получение результатов от рабочих
    for r := 1; r <= 5; r++ {
        fmt.Println("Получен результат:", <-results)
    }
}

Ключевые особенности и плюсы:

  • Простота создания: Горутины создаются очень легко с помощью ключевого слова go перед вызовом функции (go func()).
  • Эффективность: Легковесность горутин позволяет запускать десятки и сотни тысяч одновременно.
  • Встроенные примитивы синхронизации: Помимо каналов, Go предоставляет пакет sync с такими примитивами, как sync.Mutex (мьютексы), sync.RWMutex (мьютексы для чтения/записи), sync.WaitGroup (для ожидания завершения группы горутин) и sync.Once.
  • Планировщик Go: Рантайм Go включает собственный планировщик (scheduler), который эффективно распределяет горутины по доступным логическим ядрам CPU (потокам ОС), управляя их переключением и мультиплексированием.

Нюансы и потенциальные проблемы:

  • Утечки горутин (Goroutine Leaks): Если горутина запущена, но никогда не завершается (например, из-за блокировки на канале, который никогда не будет закрыт или прочитан), она будет продолжать потреблять ресурсы. Это может привести к исчерпанию памяти.
  • Состояние гонки (Race Conditions): Хотя каналы являются предпочтительным способом обмена данными, при прямом доступе к общим данным из нескольких горутин без должной синхронизации (мьютексов) могут возникать состояния гонки.
  • Взаимные блокировки (Deadlocks): Неправильное использование каналов или мьютексов может привести к ситуации, когда две или более горутины бесконечно ждут друг друга, блокируя выполнение программы.