Как в Go реализовать счётчик (counter) с использованием `map`?

Ответ

В Go для реализации счётчика очень удобно использовать map[T]int. Самый идиоматичный способ инкрементировать значение — это прямой инкремент. Это работает, потому что нулевое значение для типа int — это 0, и при первом обращении к несуществующему ключу он будет неявно создан с этим значением.

1. Простой способ (не потокобезопасный)

counts := make(map[string]int)

// Инкремент счётчика для ключа "apple"
// Если ключ не существует, Go вернёт нулевое значение (0), и операция станет 0++
counts["apple"]++

fmt.Println(counts["apple"]) // Выведет: 1

2. Конкурентный доступ

Стандартный map не является потокобезопасным. При одновременном доступе из нескольких горутин возникнет состояние гонки. Для решения этой проблемы есть два основных подхода:

  • sync.Mutex: Классический способ защиты разделяемого ресурса с помощью мьютекса. Подходит для большинства сценариев.
import "sync"

var mu sync.Mutex
counts := make(map[string]int)

// Внутри горутины
mu.Lock()         // Блокируем доступ
counts["key"]++
mu.Unlock()       // Освобождаем
  • sync.Map: Специализированный тип, оптимизированный для сценариев, где количество чтений значительно превышает количество записей. Он предоставляет атомарные операции и не требует явных блокировок.
import "sync"

var counts sync.Map

// Инкремент
actual, _ := counts.LoadOrStore("key", 1)
if v, ok := actual.(int); ok && v > 1 {
    counts.Store("key", v+1)
}
// Обратите внимание: работа с sync.Map для счётчика менее тривиальна, чем с мьютексом.