Go語言在設計上對同步(Synchronization,數據同步和線程同步)提供大量的支持,好比 goroutine和channel同步原語,庫層面有html
注意:當我說「類」時,是指 Go 裏的 struct(單身狗要有面向「對象」編程的覺悟)。git
Go語言裏對同步的支持主要有五類應用場景:github
注意:這裏當我說"線程"時,瞭解Go的同窗能夠自動映射到 "goroutine"(協程)。golang
關於 1和2,經過官方文檔瞭解其用法和實現。本系列的主角是 sync 下的工工具類,從 sync.Once 開始。內容分兩部分:編程
在多數狀況下,sync.Once 被用於控制變量的初始化,這個變量的讀寫一般遵循單例模式,知足這三個條件:微信
在 net 庫裏,系統的網絡配置就是存放在一個變量裏,代碼以下:網絡
package net var ( // guards init of confVal via initConfVal confOnce sync.Once confVal = &conf{goos: runtime.GOOS} ) // systemConf returns the machine's network configuration. func systemConf() *conf { confOnce.Do(initConfVal) return confVal } func initConfVal() { dnsMode, debugLevel := goDebugNetDNS() confVal.dnsDebugLevel = debugLevel // 省略部分代碼... }
上面這段代碼裏,confVal
存放數據, confOnce
控制讀寫,兩個都是 package-level 單例變量。因爲 Go 裏變量被初始化爲默認值,confOnce
能夠被當即使用,咱們重點關注confOnce.Do
。首先當作員函數 Do
的定義:函數
func (o *Once) Do(f func())
Do
接收一個函數做爲參數,該函數不接受任務參數,不返回任何參數。具體作什麼由使用方決定,錯誤處理也由使用方控制。工具
once.Sync
可用於任何符合 "exactly once" 語義的場景,好比:ui
Go語言中,文件被重複關閉會報error,而 channel 被重複關閉報 panic,once.Sync
能夠保證這類事情不發生,可是不能保證其餘業務層面的錯誤。下面這個例子給出了一種錯誤處理的方式,供你們參考:
// source: os/exec/exec.go package exec type closeOnce struct { *os.File once sync.Once err error } func (c *closeOnce) Close() error { c.once.Do(c.close) return c.err } func (c *closeOnce) close() { c.err = c.File.Close() }
sync.Once 類經過一個鎖變量和原子變量保障 exactly once
語義,直接擼下源碼(爲了便於閱讀,作了簡化處理):
package sync import "sync/atomic" type Once struct { done uint32 m Mutex } func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 0 { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } } }
這裏 done
是一個狀態位,用於判斷變量是否初始化完成,其有效值是:
done
默認值就是0f
不會被再次執行而 m Mutex
用於控制臨界區的進入,保證同一時間點最多有一個 f
在執行。
done
在 m.Lock()
先後的兩次校驗都是必要的。
在 Scala 裏,有一個關鍵詞 lazy
,實現了 sync.Once 一樣的功能。具體實現上,早期版本使用了 volatile 修飾狀態變量 done
,使用 synchronized
替代 m Mutex
;後來,也改爲了基於CAS的方式。
使用體驗上,顯然 lazy
更香!
掃碼關注微信公衆號「深刻Go語言」