sync.Map

2 мая 2020 г. Sync.map Map Concurrency


sync.Map

Давайте рассмотрим использование sync.Map и его исходный код.

Интерфейс

Следующие методы доступны для работы с sync.Map:

  • Load(key interface{}) (value interface{}, ok bool)
  • Store(key, value interface{})
  • Delete(key interface{})
  • Range(f func(key, value interface{}) bool)
  • LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)

Это покрывает все случаи использования карты — вставка, чтение, удаление и итерация. Также есть бонусный метод LoadOrStore, который позволяет установить значение по ключу, если значение не было установлено ранее.

Для выполнения итерации есть Range, который получает функцию, вызываемую для каждого элемента карты. Если эта функция возвращает false, итерация будет остановлена.

При виде названия sync.Map можно подумать, что sync.Map — это простая карта с некоторыми функциями пакета sync. Но на самом деле sync.Map намного сложнее. Давайте погрузимся в исходный код и выясним, как это работает.

Исходный код

Структура sync.Map выглядит следующим образом:

type Map struct {
	mu sync.Mutex

	read atomic.Value
	dirty map[interface{}]*entry
	misses int
}

type readOnly struct {
	m       map[interface{}]*entry
	amended bool
}

read — указатель на структуру readOnly. Эта структура хранит часть данных, которые используются для чтения данных по ключу или для проверки наличия данных за этим ключом. В read нет вставок, поэтому при доступе к read не используется мьютекс.

dirty — карта, которая хранит другую часть элементов. Она обычно используется для размещения новых элементов и также для чтения. В этом случае мьютекс mu используется при доступе к dirty.

Таким образом, sync.Map имеет две внутренние карты (read и dirty). Используя одну карту только для чтения, а вторую для записи, sync.Map пытается избежать использования мьютекса, когда это возможно. Давайте рассмотрим операции sync.Map.

Чтение

Сначала обращаются к read, чтобы проверить, есть ли данные за ключом. Если они есть — данные возвращаются. Это самый простой сценарий — вернуть данные из read.

Затем выполняется поиск в dirty — мьютекс mu активируется заранее. misses — счетчик обращений к dirty — увеличивается. Важная деталь — если значение этого счетчика больше количества элементов в dirty, элементы из dirty будут скопированы в read, и счетчик будет сброшен. Копирование произойдет сразу после доступа к dirty — в этот момент активируется mu.

Чтобы избежать лишнего доступа к dirty, в структуре readOnly (которая является read) есть поле amended. amended = true означает, что в sync.Map есть непустая карта dirty.

Таким образом, чтение из sync.Map наиболее эффективно, когда оно только для чтения. В этом случае это чтение равно простому чтению карты (без мьютекса). Если есть только чтение из большой карты, настроенной заранее, sync.Map хорошо подходит для этого случая.

Обновление

Сначала выполняется проверка read — есть ли данные за ключом. Если да — значение обновляется с помощью atomic.CompareAndSwap.

Если данных, прочитанных по ключу, нет, выполняется то же чтение в dirty. Если dirty не инициализирована — она будет инициализирована.

Таким образом, обновление уже существующего ключа простое, но добавление данных с новым ключом сложное.

Итерация

Перед итерацией dirty копируется в read, если dirty что-то хранит. Это делается с активированным мьютексом mu.

Затем выполняется итерация по read — простая итерация по карте.

Резюме

sync.Map — это сложная структура, состоящая в основном из двух карт — одной для чтения и одной для новых элементов. sync.Map не только близок к map+sync.RWMutex по показателям скорости, но также может быть лучшим в случае только чтения. Когда есть и чтение, и обновление, sync.Map будет иметь элементы как в read, так и в dirty. В этом случае sync.Map работает хуже, чем map+sync.RWMutex, из-за чтения из двух внутренних карт. Кроме того, есть счетчик, который нужно обновлять при доступе к dirty.

Tags:

Похожие статьи

2 Apr 2020

Обработка данных в конкурентных программах

Обработка данных в конкурентных программах

В Go у нас есть функциональность горутин из коробки. Мы можем запускать код параллельно. Однако в нашем параллельно выполняющемся коде мы можем работать с общими переменными, и не совсем понятно, как именно Go обрабатывает такие ситуации.

Read More → Map Sources
2 Apr 2020

Принципы работы типа map в GO

Принципы работы типа map в GO

Программный интерфейс map в Go описан в блоге Go. Нам просто нужно вспомнить, что map — это хранилище ключ-значение, и оно должно извлекать значения по ключу как можно быстрее.

Read More → Map Sources