Тип sync.RWMutex

Тип sync.RWMutex — это мьютекс с возможностью разделяемой блокировки для чтения. Он позволяет множественным горутинам одновременно получать доступ на чтение, но блокирует доступ на запись во время операций чтения. Такой подход значительно повышает производительность при частом чтении данных и редкой записи.

У типа sync.RWMutex есть два основных метода для блокировки: RLock для чтения и Lock для записи. Для снятия блокировок используются методы RUnlock и Unlock соответственно.

Синтаксис

govar mu sync.RWMutex

// Для чтения
mu.RLock()
// чтение данных
mu.RUnlock()

// Для записи
mu.Lock()
// запись данных
mu.Unlock()

Пример использования RWMutex для чтения

Давайте создадим структуру счетчика с разделяемым доступом и организуем одновременное чтение данных:

gopackage main

import (
  "fmt"
  "sync"
)

type Counter struct {
  mu  sync.RWMutex
  value int
}

func (c *Counter) Read() int {
  c.mu.RLock()
  defer c.mu.RUnlock()
  return c.value
}

func main() {
  counter := &Counter{value: 42}
  var wg sync.WaitGroup

  for i := 0; i < 5; i++ {
    wg.Add(1)
    go func() {
      defer wg.Done()
      res := counter.Read()
      fmt.Println(res)
    }()
  }

  wg.Wait()
}

Результат выполнения кода:

go"42"
"42"
"42"
"42"
"42"

Пример использования RWMutex для записи

Теперь добавим метод записи и проверим, как блокировка на запись влияет на чтение:

gopackage main

import (
  "fmt"
  "sync"
)

type Counter struct {
  mu  sync.RWMutex
  value int
}

func (c *Counter) Write(val int) {
  c.mu.Lock()
  defer c.mu.Unlock()
  c.value = val
  fmt.Println("Written:", val)
}

func (c *Counter) Read() int {
  c.mu.RLock()
  defer c.mu.RUnlock()
  return c.value
}

func main() {
  counter := &Counter{value: 10}
  var wg sync.WaitGroup

  wg.Add(1)
  go func() {
    defer wg.Done()
    counter.Write(99)
  }()

  wg.Add(1)
  go func() {
    defer wg.Done()
    res := counter.Read()
    fmt.Println("Read:", res)
  }()

  wg.Wait()
}

Результат выполнения кода (порядок может отличаться):

go"Written: 99"
"Read: 99"

Пример с несколькими читателями и одним писателем

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

gopackage main

import (
  "fmt"
  "sync"
  "time"
)

func main() {
  var mu sync.RWMutex
  data := "initial"

  // Читатели
  for i := 0; i < 3; i++ {
    go func(id int) {
      mu.RLock()
      fmt.Printf("Reader %d: %s\n", id, data)
      time.Sleep(100 * time.Millisecond)
      mu.RUnlock()
    }(i)
  }

  // Писатель
  time.Sleep(50 * time.Millisecond)
  go func() {
    mu.Lock()
    data = "updated"
    time.Sleep(200 * time.Millisecond)
    mu.Unlock()
  }()

  time.Sleep(500 * time.Millisecond)
}

Результат выполнения кода (порядок строк может отличаться):

go"Reader 2: initial"
"Reader 0: initial"
"Reader 1: initial"

Смотрите также

  • тип sync.Mutex,
    который предоставляет обычную блокировку без разделения на чтение/запись
  • метод RLock,
    который блокирует мьютекс для чтения
  • метод Lock,
    который блокирует мьютекс для записи
  • пакет atomic,
    который предоставляет атомарные операции для конкурентного доступа без блокировок