Ответ
Основная опасность такой последовательности — взаимоблокировка (deadlock). Это происходит из-за несогласованного порядка захвата блокировок в разных горутинах.
Сценарий взаимоблокировки
Представим две горутины:
- Горутина А: Сначала захватывает
RWMutex
на чтение (rwMu.RLock()
), а затем пытается захватить обычныйMutex
(mu.Lock()
). - Горутина Б: Сначала захватывает
Mutex
(mu.Lock()
), а затем пытается захватитьRWMutex
на запись (rwMu.Lock()
).
Возникает "смертельное объятие":
- Горутина А захватила
rwMu
и ждет, пока Горутина Б освободитmu
. - Горутина Б захватила
mu
и ждет, пока Горутина А (и все остальные читатели) освободитrwMu
, чтобы получить блокировку на запись.
В итоге обе горутины вечно ждут друг друга.
var (
rwMu sync.RWMutex
mu sync.Mutex
)
// Горутина А
go func() {
rwMu.RLock()
fmt.Println("Горутина А: RLock получен")
time.Sleep(100 * time.Millisecond)
mu.Lock() // <-- Будет ждать вечно
fmt.Println("Горутина А: Mutex получен")
mu.Unlock()
rwMu.RUnlock()
}()
// Горутина Б
go func() {
mu.Lock()
fmt.Println("Горутина Б: Mutex получен")
time.Sleep(100 * time.Millisecond)
rwMu.Lock() // <-- Будет ждать вечно
fmt.Println("Горутина Б: RWMutex Lock получен")
rwMu.Unlock()
mu.Unlock()
}()
Рекомендация
Чтобы избежать взаимоблокировок, всегда захватывайте мьютексы в строго определенном, глобальном порядке во всех горутинах. Например, договориться, что mu
всегда должен блокироваться до rwMu
. Избегайте смешивания разных типов мьютексов без четко продуманной стратегии.