14 вопросов
Пакет sync предоставляет примитивы синхронизации: Mutex, RWMutex, WaitGroup, Once, Pool. Необходимы для безопасной работы с общими данными из нескольких горутин.
sync.Mutex? 🟢 Лёгкий
▶
Мьютекс обеспечивает взаимное исключение: только одна горутина может держать блокировку. mu.Lock() / mu.Unlock() защищают критическую секцию. Типичный паттерн: mu.Lock(); defer mu.Unlock().
Подробнее →sync.WaitGroup? 🟢 Лёгкий
▶
WaitGroup - счётчик горутин. Add(n) увеличивает счётчик, Done() уменьшает на 1 (обычно через defer), Wait() блокирует, пока счётчик не достигнет 0.
Подробнее →atomic.Int64 инкапсулирует значение и предоставляет методы (Add, Load, Store, CompareAndSwap). Не нужно помнить про & и выравнивание.
Подробнее →sync.Once? 🟡 Средний
▶
once.Do(f) выполняет функцию f только один раз, даже при вызовах из нескольких горутин. Все остальные вызовы Do блокируются, пока первый не завершится. Используется для ленивой инициализации.
Подробнее →sync.RWMutex отличается от sync.Mutex? 🟡 Средний
▶
RWMutex разделяет блокировки на чтение (RLock/RUnlock) и запись (Lock/Unlock). Несколько горутин могут одновременно читать, но запись эксклюзивна. Эффективнее Mutex, когда чтений больше записей.
Подробнее →sync.OnceValue[T](f func() T) (Go 1.21+)? 🟡 Средний
▶
sync.OnceValue возвращает функцию, которая при первом вызове выполнит f и запомнит результат. Все последующие вызовы возвращают кешированное значение без повторного вычисления. Потокобезопасна. sync.OnceValues - для функций с двумя результатами.
Подробнее →var wg sync.WaitGroup; for i := 0; i < 5; i++ { go func() { wg.Add(1); work(); wg.Done() }() }; wg.Wait()? 🟡 Средний
▶
wg.Add(1) внутри горутины - гонка: wg.Wait() может завершиться до того, как горутины вызовут Add. Правильно: wg.Add(1) перед go func(). Или wg.Add(5) перед циклом. Без этого Wait() может вернуть 0 и программа завершится раньше горутин.
Подробнее →sync.Cond позволяет горутинам ждать наступления условия. Wait() отпускает мьютекс и блокируется. Signal() будит одну ждущую горутину, Broadcast() - все.
Подробнее →Возможны ложные пробуждения (spurious wakeups). Паттерн: for !condition() { cond.Wait() }. После пробуждения проверяем условие заново.
Подробнее →sync.Mutex? 🔴 Сложный
▶
Копирование мьютекса копирует его внутреннее состояние (заблокирован/нет). Копия может быть в заблокированном состоянии, что приведёт к deadlock. go vet обнаруживает копирование мьютексов.
Подробнее →sync.Once.Do(f) вызовет panic? 🔴 Сложный
▶
sync.Once гарантирует однократный вызов. Даже если f паникует, Once считается выполненным. Повторные Do() ничего не сделают. Если инициализация обязательна - обрабатывайте panic внутри f или используйте другой подход.
Подробнее →wg.Done() больше раз, чем wg.Add()? 🔴 Сложный
▶
Done() уменьшает внутренний счётчик на 1. Если счётчик уходит ниже нуля - panic. Всегда вызывайте Add(n) до запуска горутин и Done() ровно n раз. Используйте defer wg.Done() в начале горутины для гарантии.
Подробнее →sync.Mutex из другой горутины (не из той, где был Lock)? 🔴 Сложный
▶
sync.Mutex не привязан к горутине (не reentrant). Любая горутина может вызвать Unlock(). Это значит, что повторный Lock() в той же горутине вызовет deadlock - мьютекс не рекурсивный. Отличие от мьютексов в Java/C#.
Подробнее →sync.Map оптимизирован для: 1) ключи стабильны (много чтений, мало записей); 2) разные горутины работают с разными ключами. Для частых записей обычный map + sync.RWMutex эффективнее. sync.Map не типизирован (работает с any), что является его минусом.
Подробнее →