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

Какие знаешь паттерны многопоточности?

1.8 Middle🔥 171 комментариев
#Конкурентность и горутины

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

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

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

Паттерны многопоточности в Go

Как опытный Go-разработчик, я выделяю несколько ключевых паттернов многопоточности, которые органично встроены в философию языка и активно используются в production-окружении. Go с его goroutines и channels предлагает уникальный подход к конкурентности, отличный от традиционных threading-моделей.

Основные паттерны конкурентности

1. Worker Pool (Пул воркеров)

Один из самых распространенных паттернов, который позволяет контролировать количество одновременно выполняемых задач и избегать overload системы.

package main

import (
    "fmt"
    "sync"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()
    for job := range jobs {
        results <- job * 2
        fmt.Printf("Worker %d processed job %d\n", id, job)
    }
}

func main() {
    const numJobs = 10
    const numWorkers = 3
    
    jobs := make(chan int, numJobs)
    results := make(chan int, numJobs)
    var wg sync.WaitGroup
    
    for w := 1; w <= numWorkers; w++ {
        wg.Add(1)
        go worker(w, jobs, results, &wg)
    }
    
    for j := 1; j <= numJobs; j++ {
        jobs <- j
    }
    close(jobs)
    
    go func() {
        wg.Wait()
        close(results)
    }()
    
    for result := range results {
        fmt.Println("Result:", result)
    }
}

2. Fan-Out/Fan-In

Паттерн для параллельной обработки и агрегации результатов. Fan-out - распределение задач между несколькими goroutines, fan-in - сбор результатов.

func fanOut(in <-chan int, out []chan int) {
    defer func() {
        for _, ch := range out {
            close(ch)
        }
    }()
    
    for val := range in {
        for _, ch := range out {
            ch <- val
        }
    }
}

func fanIn(inputs ...<-chan int) <-chan int {
    out := make(chan int)
    var wg sync.WaitGroup
    
    for _, in := range inputs {
        wg.Add(1)
        go func(ch <-chan int) {
            defer wg.Done()
            for val := range ch {
                out <- val
            }
        }(in)
    }
    
    go func() {
        wg.Wait()
        close(out)
    }()
    
    return out
}

3. Pipeline (Конвейер)

Цепочка обработчиков, где каждый этап выполняется в отдельной goroutine, а данные передаются через каналы.

func generator(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        for n := range in {
            out <- n * n
        }
        close(out)
    }()
    return out
}

func sum(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        total := 0
        for n := range in {
            total += n
        }
        out <- total
        close(out)
    }()
    return out
}

4. Producer-Consumer (Производитель-Потребитель)

Классический паттерн, где одни goroutines производят данные, а другие их потребляют.

type Task struct {
    ID   int
    Data string
}

func producer(tasks chan<- Task, count int) {
    for i := 0; i < count; i++ {
        tasks <- Task{ID: i, Data: fmt.Sprintf("task-%d", i)}
    }
    close(tasks)
}

func consumer(id int, tasks <-chan Task, done chan<- bool) {
    for task := range tasks {
        fmt.Printf("Consumer %d processing %s\n", id, task.Data)
        time.Sleep(time.Millisecond * 100)
    }
    done <- true
}

Специфические паттерны Go

5. Context Pattern

Использование context.Context для управления временем жизни goroutines, отмены операций и передачи значений.

func workerWithContext(ctx context.Context, data chan int) {
    for {
        select {
        case <-ctx.Done():
            fmt.Println("Worker stopped:", ctx.Err())
            return
        case val := <-data:
            fmt.Println("Processing:", val)
        }
    }
}

6. Select with Timeout/Ticker

Паттерн для обработки таймаутов и периодических операций.

func operationWithTimeout() {
    result := make(chan string)
    
    go func() {
        time.Sleep(2 * time.Second)
        result <- "operation completed"
    }()
    
    select {
    case res := <-result:
        fmt.Println(res)
    case <-time.After(1 * time.Second):
        fmt.Println("operation timeout")
    }
}

7. Mutex/RWMutex для shared state

Когда необходима защита общего состояния, используем sync.Mutex или sync.RWMutex.

type SafeCounter struct {
    mu    sync.RWMutex
    value int
}

func (c *SafeCounter) Increment() {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.value++
}

func (c *SafeCounter) Value() int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.value
}

Ключевые принципы и best practices:

  • "Do not communicate by sharing memory; instead, share memory by communicating" - фундаментальный принцип Go
  • Всегда закрывайте каналы, чтобы избежать deadlock
  • Используйте buffered channels для управления пропускной способностью
  • sync.WaitGroup для ожидания завершения группы goroutines
  • sync.Once для однократного выполнения
  • Atomic operations для простых атомарных операций
  • Избегайте goroutine leak - всегда обеспечивайте выход из goroutines

Каждый из этих паттернов решает конкретные проблемы: управление ресурсами, распределение нагрузки, организация обработки данных. Выбор паттерна зависит от конкретной задачи: для CPU-bound операций подойдет worker pool, для IO-bound - fan-out/fan-in, для цепочек обработки - pipeline. Важно помнить, что избыточная конкурентность может ухудшить производительность из-за накладных расходов на переключение контекста.