Ответ
В Go для управления конкурентным выполнением кода используются следующие ключевые примитивы из пакета sync
и встроенные возможности языка:
-
Каналы (channels) — основной способ безопасной коммуникации и обмена данными между горутинами. Они обеспечивают синхронизацию, так как операция отправки или получения блокируется до тех пор, пока на другом конце не будет готова принимающая или отправляющая сторона.
// Небуферизованный канал ch := make(chan int) go func() { ch <- 42 // Отправка блокируется, пока кто-то не прочитает }() val := <-ch // Чтение блокируется, пока кто-то не отправит
-
sync.WaitGroup
— счётчик, который позволяет ожидать завершения выполнения группы горутин. Основные методы:Add()
для увеличения счётчика,Done()
для уменьшения иWait()
для блокировки до тех пор, пока счётчик не станет равен нулю.var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) // Увеличиваем счетчик go func() { defer wg.Done() // Уменьшаем по завершении // ... какая-то работа }() } wg.Wait() // Ожидаем, пока все горутины завершатся
-
sync.Mutex
иsync.RWMutex
— мьютексы для защиты критических секций (участков кода, работающих с общими данными).Mutex
предоставляет эксклюзивную блокировку (только одна горутина может владеть блокировкой).RWMutex
позволяет либо одного "писателя", либо множество "читателей", что повышает производительность в сценариях с частым чтением и редкой записью.
var mu sync.Mutex var counter int mu.Lock() // Захватываем блокировку counter++ mu.Unlock() // Освобождаем
-
sync.Once
— гарантирует, что определённый участок кода будет выполнен только один раз за всё время работы программы, независимо от количества попыток вызова. Идеально подходит для инициализации синглтонов.var once sync.Once var dbConnection *DB func GetDBConnection() *DB { once.Do(func() { dbConnection = connectToDatabase() }) return dbConnection }
-
select
— оператор, который позволяет горутине ожидать выполнения нескольких операций с каналами одновременно. Это "switch
для каналов".select { case msg1 := <-ch1: fmt.Println("Получено из ch1", msg1) case ch2 <- msg2: fmt.Println("Отправлено в ch2") case <-time.After(1 * time.Second): fmt.Println("Тайм-аут") default: // Выполняется, если ни один из каналов не готов }
-
context.Context
— механизм для передачи сигналов отмены, таймаутов и request-scoped данных между горутинами. Это стандартный способ управлять жизненным циклом операций, особенно в веб-серверах и распределённых системах.ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) defer cancel() // Важно вызывать cancel, чтобы освободить ресурсы
-
sync.Cond
— условная переменная, которая позволяет горутинам ожидать наступления определённого события или условия. Используется реже, но полезна для реализации сложных сценариев синхронизации (например, producer/consumer с общим буфером). -
sync.Pool
— пул для временных объектов, который помогает снизить нагрузку на сборщик мусора. Позволяет переиспользовать выделенную память для объектов, которые часто создаются и уничтожаются.