原子操作(不可分割的操作)提供比 mutex 更輕量的同步方式,適合單一變數的簡單同步。

sync/atomic 套件#

import "sync/atomic"

var counter int64

func increment() {
    atomic.AddInt64(&counter, 1)
}

func getCount() int64 {
    return atomic.LoadInt64(&counter)
}

常用原子操作#

var value int64 = 10

// 增減
atomic.AddInt64(&value, 5)   // value += 5
atomic.AddInt64(&value, -3)  // value -= 3

// 讀取
v := atomic.LoadInt64(&value)

// 寫入
atomic.StoreInt64(&value, 100)

// Swap:回傳舊值
old := atomic.SwapInt64(&value, 200)

// Compare-And-Swap:條件交換
swapped := atomic.CompareAndSwapInt64(&value, 200, 300)
// 若 value == 200,則設為 300,回傳 true
// 否則不改變,回傳 false

原子操作支援的型別:int32int64uint32uint64uintptrunsafe.Pointer

atomic.Value#

用於原子地儲存與載入任意型別:

var config atomic.Value

// 儲存設定
config.Store(&Config{Debug: true})

// 載入設定
cfg := config.Load().(*Config)
用 atomic.Value 實作無鎖快取
type Cache struct {
    data atomic.Value  // 儲存 map[string]string
}

func NewCache() *Cache {
    c := &Cache{}
    c.data.Store(make(map[string]string))
    return c
}

func (c *Cache) Get(key string) string {
    m := c.data.Load().(map[string]string)
    return m[key]
}

func (c *Cache) Set(key, value string) {
    for {
        oldMap := c.data.Load().(map[string]string)
        newMap := make(map[string]string, len(oldMap)+1)
        for k, v := range oldMap {
            newMap[k] = v
        }
        newMap[key] = value
        if c.data.CompareAndSwap(oldMap, newMap) {
            return
        }
        // CAS 失敗,重試
    }
}

寫入時整個 map 複製一份再 CAS 換上去,讀取端永遠不需要鎖。寫多時開銷大,但極端讀多寫少的場景非常划算。