若是兩個或者多個
goroutine
在沒有相互同步狀態的狀況下同時訪問某個資源,而且同時對這個資源進行讀寫的時候,對於這個資源就處於相互競爭狀態(race candition)。下面來看一個相互競爭的例子。
var number int var wait sync.WaitGroup func main() { wait.Add(2) go updateNumber(20000)//加20000 go updateNumber(30000)//加30000 wait.Wait() fmt.Println(number) } func updateNumber(addNumber int) { for i:=0;i<addNumber ;i++ { number ++ } wait.Done() }
上面這個例子,咱們指望獲得的值應該是500000
,可是咱們最後獲得值,並非500000
,並且每次獲得的結果是不同的。這是爲何呢?由於在兩個goroutine
中沒有同步number
的當前值,就會存在兩個goroutine
對number
值重複賦值的問題,形成值覆蓋。這樣就得不到咱們預期的結果。安全
上面的例子咱們能夠看到,若是沒有對競爭的資源進行有效的管理以及合理的處理,併發程序就會變的很複雜,而且會產生一些意想不到的錯誤。因此咱們須要對競爭資源進行管理來避免這些問題。
Go
中提供一些傳統的方式來處理這類問題
原子函數可以以很底層的加鎖機制來同步訪問整型變量和指針,咱們可使用原子函數來處理競爭問題。
var number int32 var wait sync.WaitGroup func main() { wait.Add(2) go updateNumber(20000) go updateNumber(30000) wait.Wait() fmt.Println(number) } func updateNumber(addNumber int) { defer wait.Done() for i:=0;i<addNumber ;i++ { atomic.AddInt32(&number,1) } }
這裏咱們使用了atmoic
包的AddInt32
函數。這個函數會同步整型值的加法,
方法是強制同一時刻只能有一個goroutine
運行並完成這個加法操做。當goroutine
試圖去調用任
何原子函數時,這些goroutine
都會自動根據所引用的變量作同步處理。atmoic
包中還提供了Load
與Store
方法,對資源進行安全的讀與寫。併發
另外一種方式是建立一個互斥鎖來鎖住一個區域,來保證同一個資源不會被同時修改或者使用。保證當前只有一個
goroutine
在執行當前區域的代碼。
var ( number int32 wait sync.WaitGroup mutex sync.Mutex ) func main() { wait.Add(2) go updateNumber(20000) go updateNumber(30000) wait.Wait() fmt.Println(number) } func updateNumber(addNumber int) { defer wait.Done() for i:=0;i<addNumber ;i++ { mutex.Lock() // 加鎖 number++; mutex.Unlock() //釋放鎖 } }
上面的代碼片斷,在Number
改變的先後對當前區域加鎖,最後也能獲得咱們的目的,可是這樣的會話,每次在number
變動的時候,都會建立鎖與釋放鎖,會對性能產生很大的影響。其實咱們能夠在for
循環區域來加鎖。函數
func updateNumber(addNumber int) { defer wait.Done() mutex.Lock() // 加鎖 for i:=0;i<addNumber ;i++ { number++; } mutex.Unlock() //釋放鎖 }
後面這種形式的效率明顯是要比第一種高不少的。因此咱們程序有使用互斥鎖的話,須要考慮加鎖的粒度問題。性能
雖然上面上面兩種方式也能夠解決競爭問題,可是在go
中有一種更好的方式來解決這個問題,那就是goroutine
的好兄弟channel
。因爲channel
的內容比較多,因此我將單獨寫一個筆記來記錄這方面的問題。期待下一篇的更新