Menu
CoddyTech

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() для операций записи. Это позволяет выполнять чтение одновременно, обеспечивая безопасность записи.

challenge icon

Задание

Легко

Давайте создадим потокобезопасную систему управления запасами, которая отслеживает уровни запасов товаров. Ваша система будет безопасно обрабатывать параллельные операции чтения и записи с помощью мьютексов, обеспечивая целостность данных при одновременном выполнении нескольких операций.

Вы организуете код в двух файлах:

  • inventory.go: Определите потокобезопасную систему управления запасами.

    Создайте структуру Inventory, которая хранит количества товаров в map и использует sync.RWMutex для защиты доступа. Ваша система управления запасами должна поддерживать следующие операции:

    • NewInventory() *Inventory — Создаёт новую систему управления запасами с инициализированной map
    • 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 stock
    • remove: В случае успеха выведите 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 stock

RWMutex позволяет нескольким вызовам GetStock одновременно выполнять чтение, тогда как AddStock и RemoveStock получают эксклюзивный доступ при изменении запасов.

Попробуйте сами

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" прочитать количество из ввода
		// Вызвать соответствующий метод inventory
		// Вывести результат в соответствии с описанием задания
		
		switch opType {
		case "add":
			// TODO: Прочитать количество, добавить запас, вывести результат
			
		case "get":
			// TODO: Получить запас, вывести результат
			
		case "remove":
			// TODO: Прочитать количество, удалить запас, вывести соответствующий результат
			
		}
	}
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Go