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")