Расскажите о пакете `sync` и его основных примитивах синхронизации.

Ответ

Пакет sync предоставляет основные примитивы синхронизации, необходимые для написания безопасного и корректного конкурентного кода в Go.

Основные примитивы:


  • Mutex и RWMutex: Мьютексы для эксклюзивной блокировки. sync.Mutex предоставляет полную блокировку, а sync.RWMutex — блокировку для чтения/записи, позволяя множеству горутин читать данные одновременно, но только одной — изменять их.



  • WaitGroup: Механизм для ожидания завершения работы группы горутин. Основные методы: Add(n) для увеличения счетчика, Done() для его уменьшения и Wait() для блокировки до тех пор, пока счетчик не станет равен нулю.



  • Once: Гарантирует, что определенный участок кода (функция) будет выполнен только один раз за все время работы программы, независимо от количества одновременных вызовов.



  • Pool: Пул для переиспользования объектов, что позволяет снизить нагрузку на сборщик мусора (GC). Полезен для временных объектов, которые часто создаются и уничтожаются.



  • Map: Потокобезопасная реализация map. Оптимизирована для сценариев, где ключ записывается один раз, а затем многократно читается множеством горутин. Для сценариев с частыми записями и удалениями более эффективным может быть использование обычной map с sync.Mutex.



  • Cond: Условная переменная, которая позволяет горутинам ожидать наступления определенного события или изменения условия. Работает в связке с Mutex.


Примеры:

Mutex для защиты счетчика:

var mu sync.Mutex
var counter int

func increment() {
    mu.Lock()
    defer mu.Unlock()
    counter++
}

WaitGroup для ожидания завершения воркеров:

var wg sync.WaitGroup

for i := 0; i < 5; i++ {
    wg.Add(1) // Увеличиваем счетчик перед запуском горутины
    go func(workerID int) {
        defer wg.Done() // Уменьшаем счетчик при завершении
        fmt.Printf("Воркер %d завершил работуn", workerID)
    }(i)
}

wg.Wait() // Ожидаем, пока счетчик не станет 0
fmt.Println("Все воркеры завершили работу")