Ответ
Синхронизация горутин — это координация их выполнения для обеспечения безопасного доступа к общим данным и предотвращения состояния гонки (race condition). Go предоставляет несколько встроенных механизмов для этого.
Основные механизмы синхронизации:
-
Каналы (channels)
- Назначение: Идиоматичный способ для коммуникации и синхронизации между горутинами путем передачи данных. Принцип: "Не общайтесь, разделяя память; вместо этого разделяйте память, общаясь".
- Пример:
ch := make(chan string) go func() { // Отправляем данные в канал ch <- "данные получены" }() // Блокируемся, пока не получим данные из канала msg := <-ch fmt.Println(msg)
-
Мьютексы (
sync.Mutex
иsync.RWMutex
)- Назначение: Для защиты критических секций кода, где происходит доступ к общим ресурсам.
Mutex
предоставляет эксклюзивную блокировку, аRWMutex
позволяет множественные блокировки на чтение или одну на запись. -
Пример с
Mutex
:var mu sync.Mutex var counter int // Внутри горутины mu.Lock() // Блокируем доступ counter++ mu.Unlock() // Освобождаем
- Назначение: Для защиты критических секций кода, где происходит доступ к общим ресурсам.
-
Группы ожидания (
sync.WaitGroup
)- Назначение: Для ожидания завершения работы группы горутин. Основной сценарий — запуск нескольких воркеров и ожидание их всех.
- Пример:
var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) // Увеличиваем счетчик горутин go func() { defer wg.Done() // Уменьшаем счетчик по завершении // ... какая-то работа }() } wg.Wait() // Ожидаем, пока счетчик не станет равен нулю
-
Атомарные операции (
sync/atomic
)- Назначение: Для простых операций (инкремент, декремент, сохранение, загрузка), которые должны выполняться без прерываний. Работают быстрее мьютексов, но применимы только к примитивным типам.
- Пример:
var counter int64 atomic.AddInt64(&counter, 1) // Безопасный инкремент
-
sync.Once
- Назначение: Гарантирует, что определенный участок кода выполнится только один раз за все время работы программы, например, для инициализации синглтона.
Выбор правильного инструмента зависит от задачи: для сложной коммуникации и передачи владения данными лучше подходят каналы, для защиты общих данных — мьютексы, а для простых счетчиков — атомарные операции.