Skip to content

Concurrency в Go

Go был спроектирован с учётом конкурентности как первоклассной концепции. В отличие от большинства языков, где concurrency — это библиотечная надстройка, в Go она встроена в сам язык и runtime.

TL;DR: Примитивы конкурентности

ПримитивНазначениеOverheadКогда использовать
GoroutineЕдиница параллельной работы~2KB stack, ~300ns spawnЛюбая асинхронная операция
ChannelКоммуникация между goroutines~96 bytes + bufferПередача данных/сигналов
ContextCancellation + deadline + values~48-80 bytes per ctxRequest-scoped данные, timeouts
sync.MutexMutual exclusion8 bytesЗащита shared state
sync.RWMutexRead-heavy workloads24 bytesМного читателей, мало писателей
sync.WaitGroupОжидание группы goroutines12 bytesFan-out / join patterns
sync.OnceОдноразовая инициализация12 bytesLazy singleton
sync.PoolObject reusePer-P poolsHot path allocations
sync.MapConcurrent map~2x memory vs mapRead-heavy + disjoint writes
atomicLock-free операции0 overheadCounters, flags, pointers

Обзор примитивов конкурентности

🗺️
Concurrency Overview
Интерактивная карта примитивов конкурентности Go
Открыть

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                │
└─────────────────────────────────────────────────────────────────────────────┘

Принцип выбора

go
// ✅ 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

go
// ❌ 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

go
// ❌ 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

go
// ❌ 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

bash
# 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

Go Deep Dive — книга для Senior разработчиков