РЕПЕТИТОР математика физика информатика
Для школьников и студентов. Подтягивание пробелов. ЦЭ, ЦТ, ОГЭ, ЕГЭ.
Идет набор на ЛЕТО. Жмите для подробностей:)
857 of 1593 menu

Тип sync.Map

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

Основные методы sync.Map: Store для сохранения значения, Load для получения значения по ключу, Delete для удаления ключа, LoadOrStore для атомарной загрузки или сохранения, LoadAndDelete для атомарной загрузки и удаления, Swap для атомарной замены значения, CompareAndSwap для атомарной условной замены, CompareAndDelete для атомарного условного удаления и Range для перебора всех элементов.

Создание sync.Map

Создать пустой sync.Map очень просто - достаточно объявить переменную без инициализации:

package main import ( "fmt" "sync" ) func main() { var m sync.Map fmt.Println(m) }

Результат выполнения кода:

{}

В отличие от обычных мап, sync.Map не требует инициализации через make, так как его нулевое значение уже готово к использованию.

Сохранение и получение значений

Для сохранения значения используется метод Store, который принимает ключ и значение. Для получения значения используется метод Load, который возвращает значение и флаг успешности операции:

package main import ( "fmt" "sync" ) func main() { var m sync.Map m.Store("name", "Alice") m.Store("age", 30) value, ok := m.Load("name") if ok { fmt.Println("Found:", value) } value, ok = m.Load("city") if !ok { fmt.Println("Key not found") } }

Результат выполнения кода:

"Found: Alice" "Key not found"

Метод Load возвращает два значения: само значение и булево значение, указывающее, был ли найден ключ. Если ключ не найден, значение будет нулевым для своего типа.

Атомарные операции с проверкой

Метод LoadOrStore позволяет атомарно выполнить операцию: если ключ существует, он возвращает существующее значение, если нет - сохраняет новое значение и возвращает его. Метод возвращает значение и флаг, указывающий, было ли значение загружено (true) или сохранено (false):

package main import ( "fmt" "sync" ) func main() { var m sync.Map value, loaded := m.LoadOrStore("key", "initial") fmt.Println("Value:", value, "Loaded:", loaded) value, loaded = m.LoadOrStore("key", "new") fmt.Println("Value:", value, "Loaded:", loaded) }

Результат выполнения кода:

"Value: initial Loaded: false" "Value: initial Loaded: true"

В первом случае ключа не было, поэтому значение было сохранено, и loaded вернул false. Во втором случае ключ уже существовал, поэтому значение не изменилось, и loaded вернул true.

Атомарная загрузка и удаление

Метод LoadAndDelete атомарно загружает значение по ключу и удаляет его из мапы. Он возвращает значение и флаг, указывающий, существовал ли ключ:

package main import ( "fmt" "sync" ) func main() { var m sync.Map m.Store("key", "value") value, loaded := m.LoadAndDelete("key") fmt.Println("Value:", value, "Loaded:", loaded) value, loaded = m.LoadAndDelete("key") fmt.Println("Value:", value, "Loaded:", loaded) }

Результат выполнения кода:

"Value: value Loaded: true" "Value: <nil> Loaded: false"

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

Использование в конкурентной среде

Главное преимущество sync.Map - это возможность безопасного использования из множества горутин без дополнительной синхронизации. Рассмотрим пример конкурентного увеличения счетчиков:

package main import ( "fmt" "sync" ) func main() { var m sync.Map var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { defer wg.Done() key := fmt.Sprintf("goroutine-%d", id%3) // Атомарное обновление значения for j := 0; j < 100; j++ { val, _ := m.LoadOrStore(key, 0) count := val.(int) + 1 m.Store(key, count) } }(i) } wg.Wait() m.Range(func(key, value interface{}) bool { fmt.Printf("%s: %d\n", key, value) return true }) }

Результат выполнения кода:

"goroutine-0: 400" "goroutine-1: 300" "goroutine-2: 300"

В этом примере 10 горутин конкурентно обновляют значения в sync.Map, и нам не нужно использовать мьютексы для синхронизации доступа. Метод Range используется для безопасного перебора всех элементов мапы.

Перебор всех элементов

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

package main import ( "fmt" "sync" ) func main() { var m sync.Map m.Store(1, "one") m.Store(2, "two") m.Store(3, "three") m.Store(4, "four") m.Store(5, "five") fmt.Println("All elements:") m.Range(func(key, value interface{}) bool { fmt.Printf(" %d: %s\n", key, value) return true }) fmt.Println("First three elements:") count := 0 m.Range(func(key, value interface{}) bool { if count >= 3 { return false } fmt.Printf(" %d: %s\n", key, value) count++ return true }) }

Результат выполнения кода:

"All elements:" " 5: five" " 1: one" " 4: four" " 2: two" " 3: three" "First three elements:" " 5: five" " 1: one" " 4: four"

Важно отметить, что порядок перебора в Range не гарантируется и может меняться. В первом примере мы перебираем все элементы, во втором - останавливаем перебор после первых трех элементов.

Атомарная замена значения

Метод Swap атомарно заменяет значение по ключу и возвращает предыдущее значение (или nil, если ключа не было) и флаг, указывающий, существовал ли ключ:

package main import ( "fmt" "sync" ) func main() { var m sync.Map m.Store("key", "old") prev, loaded := m.Swap("key", "new") fmt.Println("Previous:", prev, "Loaded:", loaded) value, _ := m.Load("key") fmt.Println("Current:", value) prev, loaded = m.Swap("nonexistent", "value") fmt.Println("Previous:", prev, "Loaded:", loaded) }

Результат выполнения кода:

"Previous: old Loaded: true" "Current: new" "Previous: <nil> Loaded: false"

Метод Swap полезен, когда нужно атомарно заменить значение и получить предыдущее для дальнейшей обработки.

Условные атомарные операции

Метод CompareAndSwap позволяет атомарно заменить значение только в том случае, если текущее значение равно ожидаемому. Метод возвращает true, если замена была выполнена:

package main import ( "fmt" "sync" ) func main() { var m sync.Map m.Store("counter", 0) success := m.CompareAndSwap("counter", 0, 1) fmt.Println("CAS success:", success) value, _ := m.Load("counter") fmt.Println("Counter:", value) success = m.CompareAndSwap("counter", 0, 2) fmt.Println("CAS success:", success) value, _ = m.Load("counter") fmt.Println("Counter:", value) }

Результат выполнения кода:

"CAS success: true" "Counter: 1" "CAS success: false" "Counter: 1"

Метод CompareAndDelete аналогично удаляет значение только в том случае, если текущее значение равно ожидаемому:

package main import ( "fmt" "sync" ) func main() { var m sync.Map m.Store("key", "value") deleted := m.CompareAndDelete("key", "value") fmt.Println("Deleted:", deleted) _, ok := m.Load("key") fmt.Println("Key exists:", ok) deleted = m.CompareAndDelete("key", "value") fmt.Println("Deleted:", deleted) }

Результат выполнения кода:

"Deleted: true" "Key exists: false" "Deleted: false"

Эти методы полезны для реализации оптимистичных блокировок и безопасного обновления значений в конкурентной среде.

Смотрите также

  • метод sync.Map.Store,
    который сохраняет значение по ключу
  • метод sync.Map.Load,
    который загружает значение по ключу
  • метод sync.Map.LoadOrStore,
    который атомарно загружает или сохраняет значение
  • метод sync.Map.Range,
    который перебирает все элементы мапы
Мы используем cookie для работы сайта, аналитики и персонализации. Обработка данных происходит согласно Политике конфиденциальности.
принять все настроить отклонить