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

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

2.0 Middle🔥 241 комментариев
#Конкурентность и горутины#Основы Go

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

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

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

Реализация параллельности в Go

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

Горутины (Goroutines)

Горутины — это функция или метод, выполняемый конкурентно с другими горутинами. Они легковесны (начальный размер стека ~2 КБ, может динамически расти/уменьшаться) и управляются планировщиком Go (scheduler), а не ОС.

package main

import (
    "fmt"
    "time"
)

func main() {
    // Запуск горутины
    go func() {
        fmt.Println("Горутина работает")
    }()
    
    time.Sleep(100 * time.Millisecond) // Даем время на выполнение
}

Планировщик Go использует M:N модель, где M горутин маппируются на N потоков ОС (обычно по количеству ядер CPU). Он реализует кооперативную многозадачность с вытеснением, реагируя на:

  • Системные вызовы
  • Операции с каналами
  • Сетевые операции
  • Вызов runtime.Gosched()

Каналы (Channels)

Каналы — типизированные конвейеры для безопасной коммуникации между горутинами.

package main

import "fmt"

func worker(ch chan<- int, val int) {
    ch <- val * 2 // Отправка данных в канал
}

func main() {
    ch := make(chan int, 3) // Буферизованный канал
    
    go worker(ch, 1)
    go worker(ch, 2)
    
    fmt.Println(<-ch) // Получение данных
    fmt.Println(<-ch)
    
    close(ch) // Закрытие канала
}

Типы каналов:

  • Небуферизованные (make(chan T)) — синхронная передача
  • Буферизованные (make(chan T, n)) — асинхронная с буфером
  • Однонаправленные (chan<- T только отправка, <-chan T только получение)

Конструкции для синхронизации

select

Мультиплексирование каналов:

select {
case msg := <-ch1:
    fmt.Println("Получено из ch1:", msg)
case ch2 <- data:
    fmt.Println("Отправлено в ch2")
default:
    fmt.Println("Нет операций")
}

sync пакет

Для традиционной синхронизации:

import "sync"

var mu sync.Mutex
var wg sync.WaitGroup

func safeIncrement(counter *int) {
    mu.Lock()
    defer mu.Unlock()
    *counter++
}

func main() {
    var counter int
    wg.Add(3)
    
    for i := 0; i < 3; i++ {
        go func() {
            defer wg.Done()
            safeIncrement(&counter)
        }()
    }
    
    wg.Wait()
    fmt.Println("Counter:", counter)
}

Контекст (context)

Для управления временем жизни горутин и передачей значений:

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

select {
case <-ctx.Done():
    fmt.Println("Таймаут")
case result := <-longOperation():
    fmt.Println("Результат:", result)
}

Принципы и паттерны

  1. Генератор — функция, возвращающая канал
  2. Worker pool — пул обработчиков с общей очередью задач
  3. Fan-out/fan-in — распределение работы и сбор результатов
  4. Pipeline — цепочка обработчиков, соединенных каналами

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

  • Простота — конкурентность становится частью языка
  • Безопасность — статическая проверка типов каналов
  • Эффективность — минимум накладных расходов
  • Масштабируемость — тысячи горутин на одном ядре
  • Читаемость — явный поток данных через каналы

Пример полной программы

package main

import (
    "fmt"
    "sync"
)

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

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)
    
    // Запускаем воркеры
    var wg sync.WaitGroup
    for w := 1; w <= 3; w++ {
        wg.Add(1)
        go func(workerID int) {
            defer wg.Done()
            process(workerID, jobs, results)
        }(w)
    }
    
    // Отправляем задачи
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
    
    // Закрываем results после завершения всех воркеров
    go func() {
        wg.Wait()
        close(results)
    }()
    
    // Собираем результаты
    for result := range results {
        fmt.Println("Результат:", result)
    }
}

Таким образом, Go предлагает уникальную модель параллельности, сочетающую простоту использования с высокой производительностью, делая конкурентное программирование доступным для широкого круга разработчиков.

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