В Go, горутины - это легковесные потоки выполнения, которые работают параллельно. Однако, проблема синхронизации возникает, когда нам нужно управлять порядком выполнения горутин или синхронизировать доступ к общему ресурсу.
Существует несколько способов синхронизации горутин в Go:
1. WaitGroup: WaitGroup предоставляет возможность ожидать завершения выполнения нескольких горутин. Он представляет собой счетчик, который увеличивается каждый раз, когда мы добавляем горутину, и уменьшается каждый раз, когда горутина завершается. Мы можем использовать функции Add()
, Done()
и Wait()
для управления счетчиком и ожидания окончания выполнения горутин.
Пример:
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() // Выполняем первую горутину }() go func() { defer wg.Done() // Выполняем вторую горутину }() wg.Wait() // Продолжаем выполнение основной горутины после ожидания завершения всех горутин fmt.Println("Все горутины завершены") }
2. Каналы (Channels): Каналы предоставляют нам возможность организовать взаимодействие между горутинами с помощью отправки и получения значений. Мы можем использовать каналы для синхронизации выполнения горутин путем блокировки и ожидания данных из канала.
Пример:
package main import ( "fmt" ) func main() { ch := make(chan int) go func() { // Выполняем первую горутину ch <- 1 // Отправляем значение в канал }() go func() { // Выполняем вторую горутину ch <- 2 // Отправляем значение в канал }() // Принимаем значения из канала для синхронизации выполнения горутин <-ch <-ch // Продолжаем выполнение основной горутины после синхронизации fmt.Println("Все горутины завершены") }
3. Mutex: Mutex является механизмом блокировки, который позволяет нам синхронизировать доступ к общему ресурсу. Мы можем использовать Lock()
и Unlock()
для захвата и освобождения мьютекса соответственно.
Пример:
package main import ( "fmt" "sync" ) var ( sharedResource int mutex sync.Mutex ) func main() { var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() mutex.Lock() // Выполняем первую горутину с доступом к общему ресурсу mutex.Unlock() }() go func() { defer wg.Done() mutex.Lock() // Выполняем вторую горутину с доступом к общему ресурсу mutex.Unlock() }() wg.Wait() fmt.Println("Все горутины завершены") }
4. Атомарные операции: Go предоставляет пакет sync/atomic
, который содержит набор функций для выполнения атомарных операций. Атомарные операции - это операции, которые выполняются неделимыми единицами и гарантируют согласованность данных при конкурентном доступе.
Пример:
package main import ( "fmt" "sync/atomic" ) func main() { var count int64 go func() { // Выполняем первую горутину atomic.AddInt64(&count, 1) // Атомарно увеличиваем значение на 1 }() go func() { // Выполняем вторую горутину atomic.AddInt64(&count, 1) // Атомарно увеличиваем значение на 1 }() // Синхронизация выполнения горутин с помощью атомарной операции LoadInt64 for atomic.LoadInt64(&count) < 2 { } fmt.Println("Все горутины завершены") }
В общем, использование WaitGroup, каналов, мьютексов или атомарных операций позволяет синхронизировать выполнение горутин в Go. В зависимости от конкретной ситуации и потребностей, один из этих методов может быть предпочтительным.