Как синхронизировать горутины?

В 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. В зависимости от конкретной ситуации и потребностей, один из этих методов может быть предпочтительным.