Menu
Coddy logo textTech

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

Задание

Легко

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

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

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

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

    • NewInventory() *Inventory — Создаёт новый inventory с инициализированной map
    • AddStock(product string, quantity int) — Добавляет quantity к запасу товара (используйте exclusive lock, поскольку это modifies data)
    • GetStock(product string) int — Возвращает current запас товара или 0, если он не был found (используйте блокировку чтения, поскольку этот метод only читает data)
    • RemoveStock(product string, quantity int) bool — Удаляет quantity из запаса, если существует достаточный запас. Возвращает true при успешном выполнении и false при insufficient запасе (используйте exclusive lock)

    Не забудьте использовать defer для снятия блокировки, чтобы гарантировать правильное освобождение блокировок.

  • main.go: Считывайте operations и демонстрируйте работу вашей потокобезопасной системы inventory.

    Считайте number operations, затем обработайте каждую operation. Каждая operation содержит тип (add, get или remove), имя товара и для operations add/remove — quantity.

    Для каждой operation выведите результат:

    • add: Выведите Added [quantity] [product]
    • get: Выведите [product]: [stock] in stock
    • remove: При успешном выполнении выведите Removed [quantity] [product], а в противном случае — Insufficient stock for [product]

Будут предоставлены следующие входные data:

  • Строка 1: Number operations (целое число)
  • Следующие строки: Для каждой operation:
    • Тип operation (add, get или remove)
    • Имя товара
    • Quantity (only для operations 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 позволяет multiple вызовам GetStock одновременно выполнять чтение, тогда как AddStock и RemoveStock получают exclusive access при изменении inventory.

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

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