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

Зачем нужно разделение на чтение и запись в RWMutex

1️⃣ Как кратко ответить

RWMutex используется для повышения производительности многопоточных программ, позволяя нескольким потокам одновременно читать данные, но ограничивая доступ к записи только одним потоком. Это предотвращает гонки данных и повышает эффективность, когда операции чтения преобладают над операциями записи.

2️⃣ Подробное объяснение темы

В многопоточных приложениях часто возникает необходимость синхронизации доступа к общим ресурсам, чтобы избежать гонок данных и обеспечить корректность работы программы. Стандартный мьютекс (Mutex) позволяет только одному потоку в каждый момент времени иметь доступ к защищенному ресурсу, независимо от того, выполняется ли операция чтения или записи. Это может быть неэффективно, особенно если операции чтения преобладают над операциями записи.

RWMutex (Read-Write Mutex) решает эту проблему, предоставляя два типа блокировок: блокировку для чтения и блокировку для записи.

  • Блокировка для чтения (RLock): Позволяет нескольким потокам одновременно читать данные. Это возможно, потому что чтение не изменяет состояние данных и не мешает другим потокам выполнять аналогичные операции.

  • Блокировка для записи (Lock): Позволяет только одному потоку изменять данные в любой момент времени. Это необходимо, чтобы предотвратить гонки данных, которые могут возникнуть, если несколько потоков попытаются изменить данные одновременно.

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

package main
​
import (
	"fmt"
	"sync"
)
​
type SafeCounter struct {
	mu    sync.RWMutex
	count int
}
​
// Increment увеличивает счетчик на 1. Использует Lock для записи.
func (c *SafeCounter) Increment() {
	c.mu.Lock() // Блокировка для записи: только один поток может изменить значение.
	defer c.mu.Unlock() // Разблокировка после завершения операции.
	c.count++
}
​
// Value возвращает текущее значение счетчика. Использует RLock для чтения.
func (c *SafeCounter) Value() int {
	c.mu.RLock() // Блокировка для чтения: несколько потоков могут читать одновременно.
	defer c.mu.RUnlock() // Разблокировка после завершения операции.
	return c.count
}
​
func main() {
	counter := SafeCounter{}
​
	var wg sync.WaitGroup
​
	// Запускаем 10 горутин для увеличения счетчика.
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			counter.Increment()
		}()
	}
​
	// Запускаем 10 горутин для чтения значения счетчика.
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			fmt.Println(counter.Value())
		}()
	}
​
	wg.Wait() // Ожидаем завершения всех горутин.
}

Объяснение кода

  1. Структура SafeCounter: Определяет счетчик с полем count и мьютексом mu типа sync.RWMutex.

  2. Метод Increment:

    • Использует Lock для блокировки записи, чтобы только один поток мог изменить значение count в данный момент времени.
    • После изменения счетчика вызывается Unlock для разблокировки.
  3. Метод Value:

    • Использует RLock для блокировки чтения, позволяя нескольким потокам одновременно читать значение count.
    • После чтения вызывается RUnlock для разблокировки.
  4. Основная функция main:

    • Создает экземпляр SafeCounter.
    • Запускает 10 горутин для увеличения счетчика и 10 горутин для чтения его значения.
    • Использует sync.WaitGroup для ожидания завершения всех горутин.

Использование RWMutex позволяет эффективно управлять доступом к общим данным, минимизируя блокировки и повышая производительность в сценариях, где чтение данных происходит чаще, чем их изменение.

Тема: Конкурентность
Стадия: Tech

🔒 Подпишись на бусти автора и стань Алигатором, чтобы получить полный доступ к функционалу сайта и отслеживать свой прогресс!

Твои заметки