Как сделать потокобезопасный инкремент счетчика внутри структуры в Go?

Ответ

Для потокобезопасного инкремента (увеличения) счетчика внутри структуры в Go можно использовать sync.Mutex или пакет sync/atomic.

1. Использование sync.Mutex (для более сложных операций или когда счетчик является частью более крупного состояния):

package main

import (
    "fmt"
    "sync"
)

type CounterMutex struct {
    mu    sync.Mutex
    count int
}

func (c *CounterMutex) Increment() {
    c.mu.Lock() // Блокируем доступ к счетчику
    defer c.mu.Unlock() // Гарантируем разблокировку
    c.count++
}

func (c *CounterMutex) Get() int {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.count
}

func main() {
    counter := CounterMutex{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }
    wg.Wait()
    fmt.Printf("Счетчик с Mutex: %dn", counter.Get())
}

2. Использование sync/atomic (для простых, атомарных операций, таких как инкремент/декремент):

Пакет sync/atomic предоставляет низкоуровневые, высокопроизводительные примитивы для атомарных операций над базовыми типами данных (например, int32, int64, uint32, uint64, Pointer). Они обычно быстрее, чем мьютексы, для простых операций, так как не требуют блокировки и разблокировки, используя аппаратные инструкции процессора для атомарности.

package main

import (
    "fmt"
    "sync"
    "sync/atomic"
)

type CounterAtomic struct {
    count int64 // Для atomic операций часто используются int64/uint64
}

func (c *CounterAtomic) Increment() {
    atomic.AddInt64(&c.count, 1) // Атомарно увеличиваем значение на 1
}

func (c *CounterAtomic) Get() int64 {
    return atomic.LoadInt64(&c.count) // Атомарно читаем значение
}

func main() {
    counter := CounterAtomic{}
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.Increment()
        }()
    }
    wg.Wait()
    fmt.Printf("Счетчик с Atomic: %dn", counter.Get())
}

Выбор между Mutex и atomic:

  • sync.Mutex: Используйте, когда вам нужно защитить более сложные операции, включающие несколько шагов, или когда счетчик является частью более крупного состояния, которое должно быть изменено атомарно. Mutex обеспечивает более общую защиту.
  • sync/atomic: Идеально подходит для простых, одиночных операций, таких как инкремент, декремент, загрузка или сохранение значения. Он обеспечивает лучшую производительность за счет использования аппаратных инструкций процессора для атомарности, но применим только к базовым типам данных и простым операциям.