В чём разница между `sync.Mutex` и `sync.RWMutex` в Go?

Ответ

И Mutex, и RWMutex — это примитивы синхронизации в Go, предназначенные для защиты разделяемых данных от состояния гонки. Однако они решают эту задачу по-разному и оптимизированы для разных сценариев.

sync.Mutex (Взаимное исключение)

Mutex предоставляет эксклюзивную блокировку. Это означает, что только одна горутина может владеть блокировкой в любой момент времени, независимо от того, читает она данные или пишет.

  • Lock(): Захватывает блокировку. Если она уже занята, горутина блокируется до её освобождения.
  • Unlock(): Освобождает блокировку.

sync.RWMutex (Блокировка чтения-записи)

RWMutex предоставляет раздельную блокировку, оптимизированную для сценариев, где чтений гораздо больше, чем записей. Правило работы:

  • Либо один писатель может захватить блокировку.
  • Либо множество читателей могут одновременно захватить блокировку.

Он имеет два типа блокировок:

  • Блокировка на запись (Lock/Unlock): Работает как обычный Mutex — эксклюзивный доступ.
  • Блокировка на чтение (RLock/RUnlock): Позволяет неограниченному числу горутин одновременно читать данные, но только если нет активной блокировки на запись.

Пример:

var (
    rw   sync.RWMutex
    data int
)

// Множество горутин могут выполнять этот код одновременно
rw.RLock() 
fmt.Println("Читаем данные:", data)
rw.RUnlock()

// Только одна горутина может выполнять этот код
rw.Lock()
data++ // Запись
rw.Unlock()

Ключевые отличия и когда что использовать:

  • Сценарий:

    • Mutex: Используйте, когда операции записи происходят часто или когда логика доступа к данным сложна и проще использовать одну эксклюзивную блокировку.
    • RWMutex: Идеален для структур данных, которые редко изменяются, но часто читаются (например, кеш конфигурации).
  • Производительность:

    • RWMutex может значительно повысить производительность в read-heavy сценариях.
    • Однако RWMutex имеет больше накладных расходов, чем Mutex. Если конкуренция за ресурс низкая или операции записи частые, Mutex может оказаться быстрее.

Ответ 18+ 🔞

А, ну ты про мьютексы, да? Вот эта вся хуйня с горутинами, где они как тараканы по кухне бегают и в одну кастрюлю с борщом лезут. Так, слушай, сейчас разжую.

Вот представь: у тебя есть переменная, общая, на всех. И горутины, эти ебушки-воробушки, все к ней одновременно тянутся. Одна хочет прочитать, другая — записать, третья уже с хуя ли? Короче, пиздец, а не состояние гонки. Чтобы этого не было, нам и нужны эти замки — Mutex и RWMutex.

sync.Mutex — это как один сортир на всю стройку. Железное правило: кто первый зашёл — тот и царь. Закрылся, сделал свои дела, вышел — тогда следующий. Неважно, ты просто пописал или насрал так, что плитку сносить надо. Доступ эксклюзивный, блядь.

  • Lock() — это ты дёргаешь ручку, а она не открывается. Стоишь, ждёшь, терпения ноль ебать, пока предыдущий мудак не выйдет.
  • Unlock() — это ты вышел и дверь открыл. Следующий, заходи, страдай.

sync.RWMutex — это уже поумнее система, как библиотека. Тут правила хитрее, но логичные:

  • Либо один писатель заходит в отдельный кабинет и там творит свою хуйню. Никто его не трогает.
  • Либо много читателей могут одновременно сидеть в читальном зале и листать одну и ту же книгу. Главное — чтобы в кабинете писателя никого не было.

У него два типа ключей:

  • Ключ от кабинета писателя (Lock/Unlock) — тот же самый сортир, эксклюзив. Один в один как Mutex.
  • Ключ от читального зала (RLock/RUnlock) — взял брошюрку, почитал, положил на место. Пока писатель не пришёл, читателей может быть овердохуища.

Вот, смотри, как это в коде выглядит, не пугайся:

var (
    rw   sync.RWMutex // Наш продвинутый замок с двумя режимами
    data int          // Общая переменная, из-за которой весь сыр-бор
)

// Куча горутин может делать это одновременно — читательский режим
rw.RLock() // "Ребят, я просто посмотреть!"
value := data // Читаем данные
rw.RUnlock() // "Всё, посмотрел, свободно!"

// А вот это — эксклюзив. Только одна горутина за раз.
rw.Lock() // "Всем стоять! Я ща буду менять всё нахуй!"
data = 42 // Запись, ёпта
rw.Unlock() // "Готово, можете снова суетиться."

Так когда что брать? Сука, думай головой!

  • Берёшь Mutex, когда:

    • Записываешь часто. Ну или когда тебе похуй на оптимизацию и проще везде воткнуть один простой замок, чем думать.
    • Логика доступа — пиздопроебибна, там не разберёшь, где чтение, а где запись. Закрыл на общий замок — и спи спокойно.
  • Берёшь RWMutex, когда:

    • У тебя читают постоянно, а пишут раз в год по обещанию. Классика — кеш какой-нибудь или конфиг, который грузится при старте.
    • Вот тут он реально даёт прирост, потому что читатели друг другу не мешают.

Но! Не думай, что RWMutex — это магия. У него свои накладные расходы, управление этими двумя очередями — не халява. Если у тестя конкуренция низкая или ты пишешь каждую миллисекунду, то обычный Mutex может оказаться даже шустрее. Вот такой, блядь, парадокс.

Короче, правило простое: если не уверен — бери Mutex. А как начнёшь в профайлере видеть, что все горутины в очереди на чтение стоят — вот тогда уже думай про RWMutex. Всё, вопрос закрыт, иди работай.