sync.Mutex и sync.RWMutex
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 63 из 107.
Хотя каналы являются предпочтительным способом координации горутин в Go, иногда вам нужно защищать общие данные напрямую. Пакет sync предоставляет мьютексы — блокировки, которые гарантируют, что только одна горутина обращается к ресурсу в данный момент времени.
У sync.Mutex есть два метода: Lock() и Unlock(). Когда горутина вызывает Lock(), она получает эксклюзивный доступ. Другие горутины, вызывающие Lock(), будут заблокированы до тех пор, пока не будет вызван Unlock():
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}Использование defer c.mu.Unlock() гарантирует освобождение блокировки, даже если функция завершается досрочно или происходит паника — это распространенный и рекомендуемый паттерн.
Когда операции чтения происходят часто, а записи — редко, sync.RWMutex обеспечивает лучшую производительность. Он позволяет нескольким читателям работать одновременно, но писатели получают эксклюзивный доступ:
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock() // допускается несколько читателей
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // эксклюзивный доступ для записи
defer c.mu.Unlock()
c.data[key] = value
}Используйте RLock()/RUnlock() для операций чтения и Lock()/Unlock() для записи. Это позволяет выполнять параллельное чтение, обеспечивая при этом безопасность записи.
Задание
ЛегкоДавайте создадим потокобезопасную систему инвентаризации, которая отслеживает уровни запасов товаров. Ваша система будет безопасно обрабатывать одновременные операции чтения и записи с помощью мьютексов, обеспечивая целостность данных при одновременном выполнении нескольких операций.
Вы организуете свой код в двух файлах:
inventory.go: Определите вашу потокобезопасную систему управления инвентарем.Создайте структуру
Inventory, которая хранит количество товаров в карте (map) и используетsync.RWMutexдля защиты доступа. Ваш инвентарь должен поддерживать следующие операции:NewInventory() *Inventory— создает новый инвентарь с инициализированной картойAddStock(product string, quantity int)— добавляет количество к запасу товара (используйте эксклюзивную блокировку, так как это изменяет данные)GetStock(product string) int— возвращает текущий запас товара или 0, если товар не найден (используйте блокировку на чтение, так как это только считывает данные)RemoveStock(product string, quantity int) bool— удаляет количество из запаса, если товара достаточно. Возвращаетtrueв случае успеха,falseпри недостаточном количестве (используйте эксклюзивную блокировку)
Не забудьте использовать
deferдля разблокировки, чтобы гарантировать, что блокировки всегда освобождаются должным образом.main.go: Чтение операций и демонстрация вашего потокобезопасного инвентаря.Прочитайте количество операций, затем обработайте каждую операцию. Каждая операция имеет тип (
add,getилиremove), название продукта и, для операцийadd/remove, количество.Для каждой операции выведите результат:
add: ВыведитеAdded [quantity] [product]get: Выведите[product]: [stock] in stockremove: ВыведитеRemoved [quantity] [product]в случае успеха илиInsufficient stock for [product], если товара недостаточно
Будут предоставлены следующие входные данные:
- Строка 1: Количество операций (целое число)
- Следующие строки: Для каждой операции:
- Тип операции (
add,getилиremove) - Название продукта
- Количество (только для операций
addиremove)
- Тип операции (
Например, при вводе:
5
add
apples
50
get
apples
remove
apples
30
remove
apples
25
get
applesВаш вывод должен быть следующим:
Added 50 apples
apples: 50 in stock
Removed 30 apples
Insufficient stock for apples
apples: 20 in stockRWMutex позволяет нескольким вызовам GetStock выполнять чтение одновременно, в то время как AddStock и RemoveStock получают эксклюзивный доступ при изменении инвентаря.
Шпаргалка
Пакет sync предоставляет мьютексы для защиты общих данных, когда нескольким горутинам требуется прямой доступ к одному и тому же ресурсу.
sync.Mutex
sync.Mutex обеспечивает эксклюзивный доступ с помощью Lock() и Unlock():
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Increment() {
c.mu.Lock()
c.value++
c.mu.Unlock()
}
func (c *Counter) Value() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}Использование defer с Unlock() гарантирует, что блокировка будет снята, даже если функция завершится досрочно или возникнет паника.
sync.RWMutex
sync.RWMutex разрешает несколько одновременных читателей, но обеспечивает эксклюзивный доступ для писателей:
type Cache struct {
mu sync.RWMutex
data map[string]string
}
func (c *Cache) Get(key string) string {
c.mu.RLock() // разрешено несколько читателей
defer c.mu.RUnlock()
return c.data[key]
}
func (c *Cache) Set(key, value string) {
c.mu.Lock() // эксклюзивный доступ для записи
defer c.mu.Unlock()
c.data[key] = value
}Используйте RLock()/RUnlock() для операций чтения и Lock()/Unlock() для операций записи.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Чтение количества операций
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// Создание нового инвентаря
inventory := NewInventory()
// Обработка каждой операции
for i := 0; i < numOps; i++ {
// Чтение типа операции
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
// Чтение названия продукта
productLine, _ := reader.ReadString('\n')
product := strings.TrimSpace(productLine)
// TODO: Обработать каждый тип операции (add, get, remove)
// Для "add" и "remove" прочитать количество из ввода
// Вызвать соответствующий метод инвентаря
// Вывести результат согласно описанию задачи
switch opType {
case "add":
// TODO: Прочитать количество, добавить на склад, вывести результат
case "get":
// TODO: Получить остаток на складе, вывести результат
case "remove":
// TODO: Прочитать количество, списать со склада, вывести соответствующий результат
}
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool