Ответ
Хотя каналы являются предпочтительным способом организации взаимодействия между горутинами, пакет sync
предоставляет незаменимые инструменты для традиционной синхронизации на основе разделяемой памяти.
Я использовал следующие примитивы:
-
sync.Mutex
: Мьютекс для защиты критических секций. Он гарантирует, что только одна горутина может выполнять определенный участок кода в любой момент времени. Это необходимо для предотвращения состояния гонки при доступе к общим данным. -
sync.RWMutex
: Блокировка чтения-записи. Более производительный аналогMutex
, когда есть много читателей и редкие операции записи. Он позволяет неограниченному числу горутин одновременно читать данные, но запись блокирует всех (и читателей, и писателей). -
sync.WaitGroup
: Позволяет дождаться завершения работы группы горутин. Основные методы:Add(n)
: Увеличивает счетчик горутин наn
.Done()
: Уменьшает счетчик (обычно вызывается вdefer
внутри горутины).Wait()
: Блокирует выполнение, пока счетчик не станет равен нулю.
package main import ( "fmt" "sync" "time" ) func worker(id int, wg *sync.WaitGroup) { // Уменьшаем счетчик WaitGroup, когда горутина завершается. defer wg.Done() fmt.Printf("Worker %d startingn", id) time.Sleep(time.Second) fmt.Printf("Worker %d donen", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 3; i++ { // Увеличиваем счетчик перед запуском горутины. wg.Add(1) go worker(i, &wg) } // Ждем, пока все горутины вызовут Done(). wg.Wait() fmt.Println("All workers completed") }
-
sync.Once
: Гарантирует, что определенный участок кода будет выполнен ровно один раз за все время работы приложения. Идеально подходит для инициализации синглтонов или глобальных конфигураций. -
sync.Pool
: Кэш для временных объектов, который помогает снизить нагрузку на сборщик мусора. Часто используется для переиспользования буферов, объектов запросов и т.д.
Выбор между каналами и примитивами sync
зависит от задачи: каналы — для передачи владения данными и координации, а мьютексы — для защиты состояния.