← Назад к вопросам

В каких режимах могут работать Mutex

2.0 Middle🔥 222 комментариев
#Конкурентность и горутины

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Режимы работы Mutex в Go

В Go mutex (мьютекс, взаимное исключение) из пакета sync может работать в двух основных режимах, которые определяются методами Lock() и RLock(). Эти режимы позволяют балансировать между безопасностью параллельного доступа и производительностью.

1. Стандартный режим (эксклюзивная блокировка)

Это основной режим работы mutex, который обеспечивает полную эксклюзивность доступа к защищаемым данным. При вызове Lock():

  • Только одна горутина может захватить мьютекс
  • Все остальные горутины блокируются до вызова Unlock()
  • Гарантируется, что в критической секции находится не более одной горутины
package main

import (
    "fmt"
    "sync"
)

func main() {
    var mu sync.Mutex
    var counter int
    
    for i := 0; i < 5; i++ {
        go func(id int) {
            mu.Lock()
            counter++
            fmt.Printf("Горутина %d: counter = %d\n", id, counter)
            mu.Unlock()
        }(i)
    }
    
    // Ожидание завершения (в реальном коде используйте sync.WaitGroup)
    time.Sleep(1 * time.Second)
}

2. Режим чтения (разделяемая блокировка)

Для оптимизации сценариев, где данные часто читаются, но редко изменяются, Go предоставляет RWMutex (Read-Write Mutex). Он поддерживает два подрежима:

а) Режим чтения (RLock/RUnlock)

  • Множество горутин могут одновременно захватить мьютекс для чтения
  • Запрещены операции записи, пока хотя бы один читатель активен
  • Идеально подходит для данных, которые часто читаются

б) Режим записи (Lock/Unlock)

  • Аналогичен стандартному мьютексу - эксклюзивный доступ
  • При захвате мьютекса на запись блокируются все читатели и другие писатели
package main

import (
    "sync"
    "time"
)

var (
    rwMu    sync.RWMutex
    data    map[string]string
)

func reader(id int) {
    rwMu.RLock()
    defer rwMu.RUnlock()
    
    // Множество читателей могут работать одновременно
    _ = data["key"]
    fmt.Printf("Читатель %d завершил чтение\n", id)
}

func writer(id int) {
    rwMu.Lock()
    defer rwMu.Unlock()
    
    // Только один писатель может работать одновременно
    data["key"] = fmt.Sprintf("value_%d", id)
    fmt.Printf("Писатель %d завершил запись\n", id)
}

Ключевые отличия и рекомендации

Когда использовать стандартный Mutex:

  • При любых модификациях данных
  • Когда операции чтения и записи примерно равны по частоте
  • В простых сценариях, где производительность не критична

Когда использовать RWMutex:

  • При частых операциях чтения и редких операциях записи
  • Когда чтение данных занимает значительное время
  • В кэшах, конфигурациях и других read-heavy структурах

Важные замечания:

  1. Приоритет писателей - в Go писатели имеют приоритет над читателями, чтобы избежать голодания писателей
  2. Рекурсивные блокировки не поддерживаются в Go - попытка повторного захвата мьютекса в той же горутине приведет к deadlock
  3. Отложенная разблокировка с defer - рекомендуемый паттерн для избежания утечек блокировок
// Правильный подход с defer
func safeOperation() {
    mu.Lock()
    defer mu.Unlock()
    
    // Критическая секция
    // При любом выходе из функции мьютекс будет разблокирован
    if condition {
        return
    }
    // ...
}

Вывод

Выбор между sync.Mutex и sync.RWMutex зависит от конкретного сценария использования. RWMutex обеспечивает лучшую производительность в read-heavy workloads, но добавляет сложность. Стандартный Mutex проще и надежнее для большинства случаев. Важно помнить, что преждевременная оптимизация может привести к усложнению кода без реальных преимуществ в производительности.