1. 程式人生 > >go資料同步(sync與atomic包)

go資料同步(sync與atomic包)

Golang推薦通過channel 進行通訊和同步,但在實際開發中sync包用的也較多;另外sync下還有一個atomic包,提供了一些底層的原子操作。

原子操作atomic

atomic包(sync/atomic)提供了底層的原子級記憶體操作。

共有五種操作:增減, 比較並交換, 載入, 儲存,交換(T代表int32、int64、uint32、uint64、unitptr、pointer(沒有增減操作))

  • func LoadT(addr *T) (val T) :讀取;

  • func StoreT(addr *T, val T):寫入;

  • func AddT(addr *T, delta T) (new T):增減,返回新的值;

  • func SwapT(addr *T, new T) (old T):交換,並返回舊值;

  • func CompareAndSwapT(addr *T, old, new T) (swapped bool):比較addr中儲存的值是否與old相等,若相等則替換為新值,並返回true;否則,直接返回false。

示例程式

var sum uint32 = 100

atomic.CompareAndSwapUint32(&sum, 100, sum+1) // 101

atomic.CompareAndSwapUint32(&sum, 100, sum+1) // 101,不等沒有變化

atomic.AddUint32(&sum, 1) // 102

互斥鎖

互斥鎖用來保證在任一時刻,只能有一個例程訪問某物件。Mutex 的初始值為解鎖狀態。Mutex 通常作為其它結構體的匿名欄位使用,使該結構體具有 Lock 和 Unlock 方法。

相關函式

  • func (m *Mutex) Lock():鎖住 m,如果 m 已經被加鎖,則 Lock 將被阻塞;

  • func (m *Mutex) Unlock():解鎖 m,如果 m 未加鎖,則該操作會引發 panic;

鎖相關主要操作介面

type Locker interface {

    Lock()

    Unlock()

}

讀寫互斥鎖

RWMutex 比 Mutex 多了一個“讀鎖定”和“讀解鎖”,可以讓多個例程同時讀取某物件。RWMutex 的初始值為解鎖狀態。RWMutex 通常作為其它結構體的匿名欄位使用。

相關函式

  • func (rw *RWMutex) Lock():將 rw 設定為寫鎖定狀態,禁止其他例程讀取或寫入;

  • func (rw *RWMutex) Unlock():解除 rw 的寫鎖定狀態,如果 rw 未被寫鎖定,則該操作會引發 panic;

  • func (rw *RWMutex) RLock():將 rw 設定為讀鎖定狀態,禁止其他例程寫入,但可以讀取;

  • func (rw *RWMutex) RUnlock():解除 rw 的讀鎖定狀態,如果 rw 未被讀鎖頂,則該操作會引發 panic;

  • func (rw *RWMutex) RLocker() Locker:返回一個互斥鎖,將 rw.RLock 和 rw.RUnlock 封裝成了一個 Locker 介面;

組等待

WaitGroup 用於等待一組例程的結束。主例程在建立每個子例程的時候先呼叫 Add 增加等待計數,每個子例程在結束時呼叫 Done 減少例程計數。之後,主例程通過 Wait 方法開始等待,直到計數器歸零才繼續執行。

相關函式

  • func (wg *WaitGroup) Add(delta int):計數器增加 delta,delta 可以是負數;

  • func (wg *WaitGroup) Done():計數器減少 1;

  • func (wg *WaitGroup) Wait():等待直到計數器歸零。如果計數器小於 0,則該操作會引發 panic。

示例程式

wg := sync.WaitGroup{}

for i := 0; i < 10; i++ {

wg.Add(1)  // 進入例程前呼叫,也可在迴圈外直接加10

go func(i int) {

defer wg.Done()

fmt.Print(i, " ")

}(i)

}

wg.Wait()

條件等待

Cond條件變數是執行緒間共享的一種機制,主要包括兩個動作:

  • 一個/或多個執行緒等待"條件變數的條件成立"而掛起:wait,在 Wait 之前應當手動為 c.L 上鎖,Wait 結束後手動解鎖。為避免虛假喚醒,需要將 Wait 放到一個條件判斷迴圈中。

  • 另一個執行緒使"條件成立"(給出條件成立訊號):

    • Signal:喚醒一個等待執行緒;

    • Broadcast:喚醒所有等待執行緒

  • 為了防止競爭,條件變數的使用總是和一個互斥鎖結合在一起。

相關函式

  • func NewCond(l Locker) *Cond:建立一個條件等待

  • func (c *Cond) Broadcast():喚醒所有等待者

  • func (c *Cond) Signal():喚醒一個等待者

  • func (c *Cond) Wait():解鎖 c.L 並進入等待狀態,在被喚醒時會自動重新鎖定 c.L。

示例:條件等待

condition := false // 條件

var mu sync.Mutex

cond := sync.NewCond(&mu)

// 喚醒者

go func() {

time.Sleep(...)

mu.Lock()

condition = true // 更改條件

cond.Signal()    // 傳送通知:條件已經滿足

mu.Unlock()

}()

// 等待者

go func() {

mu.Lock()

// 檢查條件是否滿足,避免虛假通知,同時避免 Signal 提前於 Wait 執行。

for !condition {

cond.Wait() // 等待時 mu 處於解鎖狀態,喚醒時重新鎖定。

}

...

mu.Unlock()

}

單次執行

Once 的作用是多次呼叫但只執行一次。Once 只有一個方法Do(),向 Do 傳入一個函式,這個函式在第一次執行 Once.Do() 的時候會被呼叫,以後再執行 Once.Do() 將沒有任何動作,即使傳入了其它的函式,也不會被執行,如果要執行其它函式,需要重新建立一個 Once 物件。

Once 可以安全的在多個例程中並行使用。

相關函式

  • func (o *Once) Do(f func())

臨時物件池

sync.Pool物件就是一組臨時物件的集合。用於儲存那些被分配了但是沒有被使用,而未來可能會使用的值(放進Pool中的物件,會隨時被回收掉;所以如果事先Put進去100個物件,下次Get的時候發現Pool是空也是有可能的),以減小垃圾回收的壓力。

Pool是協程安全的。

相關函式

  • func (p *Pool) Get() interface{} :返回Pool中的任意一個物件;

    • 如果Pool為空,則呼叫New返回一個新建立的物件;

    • 如果沒有設定New,則返回nil;

  • func (p *Pool) Put(x interface{}):放入Pool;

  • New func() interface{}:設定New方法;

示例程式

var bytePool = sync.Pool{

  New: func() interface{} {

    b := make([]byte, 1024)

    return &b

  },

}

for i := 0; i < 10000; i++{

  obj := bytePool.Get().(*[]byte)

  _ = obj

  ...

  bytePool.Put(obj)

}

安全map

為解決map併發讀寫問題,引入了sync.map。sync.map針對相對穩定的key(讀多、寫少情況)做了優化,若是頻繁的讀寫則使用內建map與RWMutex更好。

主要函式

  • func (m *Map) Delete(key interface{})

  • func (m *Map) Load(key interface{}) (value interface{}, ok bool)

  • func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool):如果Key存在,則返回對應的值;否則新增value,並返回

  • func (m *Map) Range(f func(key, value interface{}) bool):列舉map元素並執行func,若返回false,則停止列舉

  • func (m *Map) Store(key, value interface{})

示例程式

var m sync.Map

vv, ok := m.LoadOrStore("1", "one")

fmt.Println(vv, ok) //one false

vv, ok = m.Load("1")

fmt.Println(vv, ok) //one true

vv, ok = m.LoadOrStore("1", "oneone")

fmt.Println(vv, ok) //one true

m.Store("1", "oneone")

vv, ok = m.Load("1")

fmt.Println(vv, ok) // oneone true

m.Store("2", "two")

m.Range(func(k, v interface{}) bool {

fmt.Println(k, v)

return true

})

m.Delete("1")