Concurrency в Go
Go был спроектирован с учётом конкурентности как первоклассной концепции. В отличие от большинства языков, где concurrency — это библиотечная надстройка, в Go она встроена в сам язык и runtime.
TL;DR: Примитивы конкурентности
| Примитив | Назначение | Overhead | Когда использовать |
|---|---|---|---|
| Goroutine | Единица параллельной работы | ~2KB stack, ~300ns spawn | Любая асинхронная операция |
| Channel | Коммуникация между goroutines | ~96 bytes + buffer | Передача данных/сигналов |
| Context | Cancellation + deadline + values | ~48-80 bytes per ctx | Request-scoped данные, timeouts |
| sync.Mutex | Mutual exclusion | 8 bytes | Защита shared state |
| sync.RWMutex | Read-heavy workloads | 24 bytes | Много читателей, мало писателей |
| sync.WaitGroup | Ожидание группы goroutines | 12 bytes | Fan-out / join patterns |
| sync.Once | Одноразовая инициализация | 12 bytes | Lazy singleton |
| sync.Pool | Object reuse | Per-P pools | Hot path allocations |
| sync.Map | Concurrent map | ~2x memory vs map | Read-heavy + disjoint writes |
| atomic | Lock-free операции | 0 overhead | Counters, flags, pointers |
Обзор примитивов конкурентности
CSP vs Shared Memory
Go предоставляет оба подхода, но идиоматически предпочитает CSP (Communicating Sequential Processes):
┌─────────────────────────────────────────────────────────────────────────────┐
│ Concurrency Models в Go │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ CSP (Channels) Shared Memory (sync/atomic) │
│ ═══════════════ ═══════════════════════════ │
│ │
│ ┌─────────┐ channel ┌─────────┐ ┌─────────┐ mutex ┌──────────┐ │
│ │Goroutine│────────────▶│Goroutine│ │Goroutine│◀─────▶│ Shared │ │
│ │ A │ │ B │ │ A + B │ │ State │ │
│ └─────────┘◀────────────└─────────┘ └─────────┘ └──────────┘ │
│ │
│ Ownership transfer Защита доступа │
│ "Don't communicate by "Do share memory │
│ sharing memory" carefully with locks" │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ Когда CSP: Когда Shared Memory: │
│ • Pipeline обработка • Simple counters (atomic) │
│ • Fan-out / fan-in • Caches (sync.Map) │
│ • Event broadcasting • Object pools (sync.Pool) │
│ • Request-response • Fine-grained locking │
│ • Producer-consumer • Hot path performance │
└─────────────────────────────────────────────────────────────────────────────┘Принцип выбора
// ✅ CSP: данные "перемещаются" между goroutines
func pipeline() {
jobs := make(chan Job)
results := make(chan Result)
go worker(jobs, results) // ownership transfer
jobs <- job // отдали владение
r := <-results // получили результат
}
// ✅ Shared Memory: общий кэш с редкими записями
func cache() {
var cache sync.Map
// Много читателей, редкие записи
go func() { v, _ := cache.Load("key") }()
go func() { cache.Store("key", value) }()
}Архитектура примитивов
┌─────────────────────────────────────────────────────────────────────────────┐
│ Runtime Layer │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ GMP Scheduler │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ G │ │ G │ │ G │ ... │ M │ │ P │ │ │
│ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ │
│ │ ▲ ▲ ▲ │ │ │ │
│ │ │ │ │ ▼ ▼ │ │
│ │ ┌───┴───────┴───────┴───────────────────────────────────┐ │ │
│ │ │ runtime.gopark / runtime.goready │ │ │
│ │ └───────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ Channels │ │ sync Package │ │
│ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │
│ │ │ runtime.hchan │ │ │ │ sync.Mutex │ │ │
│ │ │ • sendq │ │ │ │ sync.RWMutex │ │ │
│ │ │ • recvq │ │ │ │ sync.WaitGroup│ │ │
│ │ │ • buf │ │ │ │ sync.Once │ │ │
│ │ └───────────────┘ │ │ │ sync.Pool │ │ │
│ └─────────────────────┘ │ │ sync.Map │ │ │
│ │ │ │ sync.Cond │ │ │
│ │ │ └───────────────┘ │ │
│ │ └─────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ sync/atomic │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │atomic.Int64 │ │atomic.Bool │ │atomic.Value │ │atomic.Ptr[T]│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
│ │ │ CPU Instructions (LOCK, CAS, etc.) │ │ │
│ │ └─────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────────────────┤
│ Context Layer │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ context.Context │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │emptyCtx │ │cancelCtx │ │timerCtx │ │valueCtx │ │afterFunc │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘Структура раздела
Goroutines
Жизненный цикл горутины: от runtime.newproc до goexit. Все 10 состояний G, stack growth/shrinking, preemption (cooperative и async), LockOSThread, goroutine leaks и их детекция.
Channels
Внутренности runtime.hchan: ring buffer, sudog, алгоритмы send/receive, direct send оптимизация, close семантика. Select: scase структура, selectgo алгоритм, compiler оптимизации.
Context
Типы контекстов: emptyCtx, cancelCtx, timerCtx, valueCtx, afterFuncCtx. Cancellation propagation, lazy Done channel, Value lookup performance. Go 1.20+: Cause, Go 1.21+: WithoutCancel, AfterFunc.
sync Primitives
Внутренности sync пакета: Mutex starvation mode, RWMutex writer priority, WaitGroup atomic state, Once double-checked locking, Cond notifyList, Pool per-P + victim cache, Map read/dirty амортизация.
Atomic Operations
sync/atomic typed wrappers (Go 1.19+), memory ordering (acquire-release), CPU реализация (x86 LOCK, ARM LDREX/STREX), lock-free структуры, false sharing.
Memory Model
Go Memory Model: happens-before, синхронизация операций, data races и их последствия. Race Detector: ThreadSanitizer, shadow memory, vector clocks, CI интеграция.
Patterns
Production-ready паттерны: pipelines, fan-out/fan-in, error handling (errgroup), rate limiting (token bucket), cancellation patterns, resource management (semaphore, bounded parallelism).
Связь с GMP Scheduler
Что уже рассмотрено в разделе Runtime
Раздел GMP Scheduler покрывает:
- Структуры
runtime.g,runtime.m,runtime.p - Work Stealing алгоритм
- Local/Global Run Queues
- GOMAXPROCS и его runtime адаптация
- Scheduler Trace (
GODEBUG=schedtrace)
Здесь мы фокусируемся на использовании этих механизмов через высокоуровневые примитивы.
Performance Guidelines
Goroutines
// ❌ Avoid: goroutine per request без ограничений
for req := range requests {
go handle(req) // unbounded goroutines → OOM
}
// ✅ Better: bounded concurrency
sem := make(chan struct{}, maxWorkers)
for req := range requests {
sem <- struct{}{}
go func(r Request) {
defer func() { <-sem }()
handle(r)
}(req)
}Channels
// ❌ Avoid: channel для single value
ch := make(chan Result, 1)
go func() { ch <- compute() }()
result := <-ch
// ✅ Better: direct return через closure
var result Result
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
result = compute() // direct write, no channel overhead
}()
wg.Wait()sync.Pool
// ❌ Avoid: Pool для долгоживущих объектов
var connPool = sync.Pool{...} // connections могут быть evicted в любой момент
// ✅ Better: Pool для temporary buffers
var bufPool = sync.Pool{
New: func() any { return make([]byte, 4096) },
}
func process(data []byte) {
buf := bufPool.Get().([]byte)
defer bufPool.Put(buf)
// use buf
}Debugging & Tracing
# Goroutine dump
kill -SIGQUIT <pid>
# или
curl localhost:6060/debug/pprof/goroutine?debug=2
# Scheduler trace
GODEBUG=schedtrace=1000 ./app
# Race detector
go run -race ./...
go test -race ./...
# Trace
go test -trace trace.out
go tool trace trace.out