← Назад к вопросам
Чем отличается sync.Mutex от sync.RWMutex?
2.2 Middle🔥 211 комментариев
#Конкурентность и горутины
Комментарии (1)
🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Основное отличие: назначение и гранулярность блокировок
Основное и фундаментальное отличие между sync.Mutex и sync.RWMutex заключается в гранулярности предоставляемых блокировок и, как следствие, в сценариях их применения.
sync.Mutexпредоставляет взаимоисключающую (exclusive) блокировку. Это классический мьютекс, который в любой момент времени может удерживать только одна горутина. Он гарантирует, что доступ к защищаемым данным (критической секции) получает ровно один поток исполнения. Все остальные горутины, пытающиеся захватить этот мьютекс, будут заблокированы до его освобождения.sync.RWMutex(Read-Write Mutex) предоставляет два типа блокировок: на чтение (RLock) и на запись (Lock). Это позволяет нескольким горутинам одновременно захватывать блокировку на чтение, но блокировка на запись остается взаимоисключающей, как у обычногоMutex. Таким образом, RWMutex оптимизирован для сценариев, где операции чтения значительно превосходят по количеству операции записи.
Детальное сравнение
sync.Mutex: Простая эксклюзивная блокировка
Методы:
Lock()– захватывает мьютекс. Если он уже захвачен другой горутиной, текущая горутина блокируется.Unlock()– освобождает мьютекс.
Пример использования:
package main
import (
"fmt"
"sync"
)
type SafeCounter struct {
mu sync.Mutex
v map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock() // Захват эксклюзивной блокировки
defer c.mu.Unlock()
c.v[key]++ // И запись, и чтение защищены одной блокировкой
}
func (c *SafeCounter) Value(key string) int {
c.mu.Lock() // Даже для чтения нужна эксклюзивная блокировка
defer c.mu.Unlock()
return c.v[key]
}
sync.RWMutex: Разделение блокировок на чтение и запись
Методы:
Lock()/Unlock()– для эксклюзивной блокировки на запись. Работают аналогичноsync.Mutex.RLock()/RUnlock()– для неэксклюзивной блокировки на чтение. Многие горутины могут одновременно захватитьRLock().- Важное правило: если хотя бы одна горутина держит блокировку на чтение (
RLock), то захватить блокировку на запись (Lock) невозможно. И наоборот, если горутина держит блокировку на запись, то захватить блокировку на чтение невозможно.
Пример использования:
type SafeConfig struct {
mu sync.RWMutex
config map[string]string
}
func (c *SafeConfig) Update(key, value string) {
c.mu.Lock() // Эксклюзивная блокировка ТОЛЬКО для операции записи
defer c.mu.Unlock()
c.config[key] = value
}
func (c *SafeConfig) Get(key string) string {
c.mu.RLock() // Неэксклюзивная блокировка на чтение. Многие горутины могут читать параллельно.
defer c.mu.RUnlock()
return c.config[key]
}
Ключевые различия в поведении
| Аспект | sync.Mutex | sync.RWMutex |
|---|---|---|
| Тип блокировки | Один тип: эксклюзивная. | Два типа: эксклюзивная запись (Lock) и неэксклюзивное чтение (RLock). |
| Параллельное чтение | Невозможно. Любая операция (чтение или запись) требует полной эксклюзивности. | Возможно. Множество горутин могут одновременно читать данные, удерживая RLock. |
| Производительность | Проще, меньше накладных расходов на одну операцию. | Имеет более сложную внутреннюю логику для управления очередями читателей и писателей. В сценариях с частыми записями или низкой конкуренцией может быть МЕДЛЕННЕЕ обычного Mutex из-за накладных расходов. |
| Сценарий использования | Подходит для любых критических секций, особенно когда операции записи часты или код выполняет чтение-модификацию-запись (read-modify-write). | Идеален для сценариев "частое чтение, редкая запись" (read-heavy workloads). Например, кэши, конфигурации, справочники, которые редко обновляются. |
Важные практические замечания
- Предпочтение
sync.Mutexпо умолчанию: Начинайте проектирование с простогоsync.Mutex. Вводитеsync.RWMutexтолько после измерений профилировщиком, которые явно показывают, что contention (состязание) за блокировку чтения является узким местом, и что операции чтения доминируют. - Цена
RWMutex: Внутренняя реализацияRWMutexсложнее. Ему необходимо отслеживать количество активных читателей, управлять приоритетом (в современных версиях Go писатели имеют приоритет, чтобы не происходило starvation — голодания писателей). Из-за этого вызовRLock()/RUnlock()может быть сопоставим по стоимости с вызовомLock()/Unlock()при отсутствии конкуренции. - Семантика:
RWMutexне гарантирует, что читатели увидят "самые свежие" данные, записанные другим писателем, если между операциями чтения и записи нет дополнительных механизмов синхронизации (каналы,sync.WaitGroupи т.д.). Однако он гарантирует, что операции атомарны и данные не будут повреждены. - Блокировка на запись при наличии читателей: Писатель будет ждать, пока все существующие читатели освободят блокировку (
RUnlock()), прежде чем он сможет захватитьLock(). - Блокировка на чтение при наличии писателя: Новые читатели будут ждать, пока писатель, удерживающий
Lock(), не освободит его.
Вывод
sync.Mutex — это базовый и универсальный инструмент для обеспечения эксклюзивного доступа. sync.RWMutex — это его специализированная оптимизация для конкретного, но распространенного сценария, где необходимо максимизировать параллелизм операций чтения. Выбор между ними должен быть осознанным и основанным на реальных данных о нагрузке, а не на предположениях. В сомнительных случаях sync.Mutex является более предсказуемым и зачастую достаточно эффективным выбором.