Ответ
Синхронизация горутин в Go достигается с помощью двух основных подходов: каналов (channels) и примитивов синхронизации из пакета sync
. Идиоматический Go-подход часто следует принципу "Не общайтесь, делясь памятью; делитесь памятью, общаясь" (Don't communicate by sharing memory; instead, share memory by communicating).
Основные механизмы:
-
Каналы (Channels):
- Каналы предоставляют безопасный способ обмена данными между горутинами. Они блокируют отправляющую горутину до тех пор, пока другая горутина не будет готова принять данные, и наоборот. Это обеспечивает автоматическую синхронизацию и предотвращает состояния гонки.
- Используются для передачи данных, сигнализации о событиях, координации завершения работы и т.д.
package main import "fmt" import "time" func worker(done chan bool) { fmt.Println("Рабочая горутина: Начало работы...") time.Sleep(1 * time.Second) // Имитация работы fmt.Println("Рабочая горутина: Работа завершена.") done <- true // Отправляем сигнал о завершении через канал } func main() { done := make(chan bool, 1) // Буферизованный канал для сигнала go worker(done) <-done // Блокируем main-горутину, пока не получим сигнал из канала fmt.Println("Main-горутина: Рабочая горутина завершила работу.") }
-
WaitGroup (из пакета
sync
):- Используется для ожидания завершения группы горутин. Вы добавляете счетчик для каждой запущенной горутины (
Add
), каждая горутина уменьшает счетчик по завершении (Done
), а вызывающая горутина ждет, пока счетчик не станет нулем (Wait
).
package main import ( "fmt" "sync" "time" ) func workerWG(id int, wg *sync.WaitGroup) { defer wg.Done() // Уменьшаем счетчик по завершении горутины fmt.Printf("Рабочая горутина %d: Начало работы...n", id) time.Sleep(500 * time.Millisecond) fmt.Printf("Рабочая горутина %d: Работа завершена.n", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) // Увеличиваем счетчик для каждой новой горутины go workerWG(i, &wg) } wg.Wait() // Блокируем main-горутину, пока все горутины не завершатся fmt.Println("Main-горутина: Все рабочие горутины завершили работу.") }
- Используется для ожидания завершения группы горутин. Вы добавляете счетчик для каждой запущенной горутины (
-
Mutex/RWMutex (из пакета
sync
):- Mutex (мьютекс): Используется для защиты общих данных от одновременного доступа нескольких горутин. Только одна горутина может владеть мьютексом в любой момент времени, обеспечивая эксклюзивный доступ к защищенному ресурсу.
- RWMutex (мьютекс чтения/записи): Позволяет множеству горутин читать данные одновременно, но только одной горутине записывать данные (и блокирует все чтения во время записи).
package main import ( "fmt" "sync" "time" ) var ( counter int mu sync.Mutex ) func increment() { mu.Lock() // Захватываем мьютекс defer mu.Unlock() // Освобождаем мьютекс при выходе из функции counter++ } func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() increment() }() } wg.Wait() fmt.Printf("Финальное значение счетчика: %dn", counter) }
-
Atomic операции (из пакета
sync/atomic
):- Предоставляют низкоуровневые, атомарные операции для простых типов данных (например,
int32
,int64
,uint32
,uint64
,pointer
). Эти операции гарантированно выполняются целиком, без прерываний, что делает их безопасными для использования в конкурентной среде без необходимости мьютексов для простых счетчиков или флагов.
package main import ( "fmt" "sync" "sync/atomic" ) var atomicCounter int32 func main() { var wg sync.WaitGroup for i := 0; i < 1000; i++ { wg.Add(1) go func() { defer wg.Done() atomic.AddInt32(&atomicCounter, 1) // Атомарное инкрементирование }() } wg.Wait() fmt.Printf("Финальное значение атомарного счетчика: %dn", atomicCounter) }
- Предоставляют низкоуровневые, атомарные операции для простых типов данных (например,
Выбор между каналами и примитивами sync
зависит от конкретной задачи. Каналы часто являются более идиоматичным и безопасным способом для координации и обмена данными, в то время как примитивы sync
(мьютексы, WaitGroup) более подходят для защиты общих ресурсов или ожидания завершения группы задач.