К каким последствиям может привести блокировка `sync.Mutex` после блокировки `sync.RWMutex` в разных горутинах?

Ответ

Основная опасность такой последовательности — взаимоблокировка (deadlock). Это происходит из-за несогласованного порядка захвата блокировок в разных горутинах.

Сценарий взаимоблокировки

Представим две горутины:

  1. Горутина А: Сначала захватывает RWMutex на чтение (rwMu.RLock()), а затем пытается захватить обычный Mutex (mu.Lock()).
  2. Горутина Б: Сначала захватывает 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. Избегайте смешивания разных типов мьютексов без четко продуманной стратегии.