Какие знаешь паттерны многопоточности?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Паттерны многопоточности в 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. Важно помнить, что избыточная конкурентность может ухудшить производительность из-за накладных расходов на переключение контекста.