# Горутины и каналы

> Встроенная модель конкурентности Go — лёгкие потоки (горутины), каналы для обмена данными и оператор select.

Источник: https://llmdocs.ru/wiki/goroutines-channels/ · обновлено 2026-11-09 · лицензия CC BY-SA 4.0

- **Язык**: Go
- **Модель**: CSP (взаимодействующие последовательные процессы)
- **Планировщик**: M:N, модель G-M-P
- **Начальный стек горутины**: несколько КБ, растёт динамически
- **Ключевые конструкции**: `go`, `chan`, `select`, `close`
- **Сопутствующие пакеты**: sync, context, errgroup
- **Поиск гонок**: `go test -race`
**Горутины и каналы** — встроенные в язык [Go](https://llmdocs.ru/wiki/go.md) средства конкурентного программирования. **Горутина** — функция, выполняющаяся конкурентно с остальной программой под управлением планировщика среды исполнения Go; она намного легче потока операционной системы, и программа может держать сотни тысяч горутин одновременно. **Канал** — типизированная труба, через которую горутины передают друг другу значения и синхронизируются. Девиз подхода из официальной документации: «Не общайтесь через разделяемую память — разделяйте память через общение».[1]

## Происхождение

Модель восходит к работе Тони Хоара «Взаимодействующие последовательные процессы» (CSP, 1978), где программы описываются как независимые процессы, обменивающиеся сообщениями по каналам.[3] Роб Пайк применял эти идеи в языках Newsqueak и Limbo до того, как они вошли в Go.

## Горутины

Горутина запускается ключевым словом `go` перед вызовом функции:

```go
go sendEmail(user)          // выполняется конкурентно, main продолжает работу
go func() {
    fmt.Println("анонимная горутина")
}()
```

Горутина стартует с маленьким стеком в несколько килобайт, который автоматически растёт и сжимается. Планировщик Go распределяет горутины по потокам ОС по схеме **M:N** (модель G-M-P): G — горутины, M — потоки ОС, P — логические процессоры, число которых по умолчанию равно числу ядер (`GOMAXPROCS`). Когда горутина блокируется на вводе-выводе или канале, поток переключается на другую. С Go 1.14 планировщик умеет принудительно прерывать долгие циклы, чтобы одна горутина не захватывала процессор.[6]

## Каналы

```go
ch := make(chan int)        // небуферизованный канал
buf := make(chan string, 10) // буферизованный на 10 элементов

ch <- 42      // отправить (блокируется, пока кто-то не примет)
v := <-ch     // принять
close(ch)     // закрыть: новых значений не будет
for v := range ch { ... }   // читать до закрытия
```

- **Небуферизованный** канал синхронизирует отправителя и получателя: отправка завершается только в момент приёма.
- **Буферизованный** канал позволяет отправить несколько значений без ожидания, пока буфер не заполнится.
- Тип можно ограничить направлением: `chan<- int` — только отправка, `<-chan int` — только приём.
- Отправка в закрытый канал вызывает панику; приём из закрытого возвращает нулевое значение и признак `ok == false`.

## Оператор select

`select` ждёт сразу несколько операций с каналами и выполняет ту, что готова первой:[2]

```go
select {
case msg := <-results:
    fmt.Println("результат:", msg)
case <-time.After(2 * time.Second):
    fmt.Println("таймаут")
case <-ctx.Done():
    return ctx.Err()   // отмена извне
}
```

## Пример: пул обработчиков

```go
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for j := range jobs {
        results <- j * j
    }
}

func main() {
    jobs, results := make(chan int, 100), make(chan int, 100)
    var wg sync.WaitGroup
    for w := 1; w <= 4; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }
    for i := 1; i <= 9; i++ { jobs <- i }
    close(jobs)
    wg.Wait()
    close(results)
    for r := range results { fmt.Println(r) }
}
```

Четыре горутины разбирают задания из общего канала; `sync.WaitGroup` ждёт их завершения.

## Сопутствующие средства

| Средство | Назначение |
|---|---|
| `sync.WaitGroup` | дождаться группы горутин |
| `sync.Mutex`, `RWMutex` | защитить общие данные, когда канал избыточен |
| `sync.Once` | однократная инициализация |
| `context.Context` | отмена и дедлайны, передаваемые по цепочке вызовов[4] |
| `errgroup.Group` (golang.org/x/sync) | группа горутин с первой ошибкой и отменой |
| `sync/atomic` | атомарные операции |

## Типичные ошибки

- **Утечка горутин** — горутина вечно ждёт канал, в который никто не пишет. Лечится отменой через `context` и закрытием каналов.
- **Гонка данных** — несколько горутин меняют переменную без синхронизации. Детектор гонок (`go run -race`, `go test -race`) находит такие места во время выполнения.[5] Подробнее — в статье [Состояние гонки и блокировки](https://llmdocs.ru/wiki/race-conditions-locks.md).
- **Взаимная блокировка** — все горутины ждут друг друга; среда исполнения сообщает «all goroutines are asleep — deadlock!».
- **Захват переменной цикла** — до Go 1.22 переменная цикла была общей для всех итераций, и горутины, созданные в цикле, могли видеть одно и то же значение. С 1.22 каждая итерация получает свою переменную.

## Сравнение с другими моделями

| Язык | Модель |
|---|---|
| Go | горутины + каналы, вытесняющий планировщик в среде исполнения |
| [Python](https://llmdocs.ru/wiki/python.md) | [asyncio](https://llmdocs.ru/wiki/asyncio.md): кооперативные сопрограммы в одном потоке |
| JavaScript | цикл событий и промисы (см. [Промисы и async/await (JS)](https://llmdocs.ru/wiki/js-promises-async.md)) |
| Rust | async/await поверх выбираемой среды исполнения (см. [async в Rust и Tokio](https://llmdocs.ru/wiki/rust-async-tokio.md)) |
| Erlang/Elixir | лёгкие процессы с почтовыми ящиками (модель акторов) |

Главное отличие Go — горутины не требуют «раскрашивать» функции в синхронные и асинхронные: любая функция может быть запущена конкурентно.

## См. также

- [Go (язык)](https://llmdocs.ru/wiki/go.md)
- [Конкурентность и параллелизм](https://llmdocs.ru/wiki/concurrency-parallelism.md)
- [Состояние гонки и блокировки](https://llmdocs.ru/wiki/race-conditions-locks.md)
- [async в Rust и Tokio](https://llmdocs.ru/wiki/rust-async-tokio.md)

## Источники

1. [Effective Go — Concurrency](https://go.dev/doc/effective_go#concurrency)
2. [The Go Programming Language Specification — Channel types, Select statements](https://go.dev/ref/spec#Channel_types)
3. [C. A. R. Hoare. Communicating Sequential Processes (1978)](https://dl.acm.org/doi/10.1145/359576.359585)
4. [Go Concurrency Patterns: Context — The Go Blog](https://go.dev/blog/context)
5. [Data Race Detector](https://go.dev/doc/articles/race_detector)
6. [Go 1.14 Release Notes — asynchronous preemption](https://go.dev/doc/go1.14)
