筆者在前文《Golang 入門 : 理解併發與並行》和《Golang 入門 : goroutine(協程)》中介紹了 Golang 對併發的原生支持以及 goroutine 的用法。本文咱們來聊聊併發與並行帶來的一些反作用。html
並行編程之因此難道較高,根本的緣由是須要處理共享資源的同步訪問。好比在 Golang 中若是兩個或者多個 goroutine 在沒有互相同步的狀況下,訪問某個共享的資源,並試圖同時讀和寫這個資源,就處於相互競爭的狀態,這種狀況被稱做競爭條件(race candition)。競爭條件的存在是讓併發程序變得複雜的地方,十分容易引發潛在問題。對一個共享資源的讀和寫操做必須是原子化的,換句話說,同一時刻只能有一個 goroutine 對共享資源進行讀和寫操做。編程
讓咱們來經過下面的 demo 來觀察 goroutine 引入的競爭條件,爲了讓觀察結果明顯,咱們採起了一些極端措施:併發
package main import ( "sync" "fmt" "runtime" ) var( // counter是全部goroutine都要增長其值的變量 counter int // wg用來等待程序結束 wg sync.WaitGroup ) // main是全部Go程序的入口 func main(){ runtime.GOMAXPROCS(1) // 計數加2,表示要等待兩個goroutine wg.Add(2) // 建立兩個goroutine go incCounter(1) go incCounter(2) // 等待goroutine結束 wg.Wait() fmt.Println("Final Counter:", counter) } // incCounter增長包裏counter變量的值 func incCounter(id int){ // 在函數退出時調用Done來通知main函數工做已經完成 defer wg.Done() for count := 0; count < 2; count++{ // 捕獲counter的值 value := counter // 當前goroutine從線程退出,並放回到隊列 runtime.Gosched() // 增長本地value變量的值 value++ // 將該值保存回counter counter = value } }
運行上面的代碼,輸出結果以下:編程語言
Final Counter: 2
上面的程序中會對變量 counter 會進行 4 次讀和寫操做,每一個 goroutine 執行兩次。可是,程序終止時,counter 變量的值爲 2。咱們能夠經過下面的圖解來理解該程序的執行過程(此圖來自互聯網):函數
每一個 goroutine 都會覆蓋另外一個 goroutine 的工做。這種覆蓋發生在 goroutine 切換的時候。每一個 goroutine 創造了一個 counter 變量的副本,以後就切換到另外一個 goroutine。當 這個 goroutine 再次運行的時候,counter 變量的值已經改變了,可是 goroutine 並無更新本身的那個副本的值,而是繼續使用這個副本的值,用這個值遞增,並存回 counter 變量,結果覆蓋了另外一個 goroutine 完成的工做。 下面是對程序執行過程的解釋:spa
// 建立兩個 goroutine go incCounter(1) go incCounter(2)
程序中通 go 關鍵字和 incCounter 函數建立了兩個 goroutine。在 incCounter 函數內部對變量 counter 進行了讀和寫操做,而 counter 變量是這個示例程序裏的共享資源。每一個 goroutine 都會先讀出這個 counter 變量的值,並把 counter 變量的副本存入一個叫做 value 的本地變量。以後 incCounter 函數對 value 變量加 1,並最終將這個新值存回到 counter 變量。incCounter 函數在對本地變量 value加 1 前調用了 runtime 包的 Gosched 函數,這個調用會將 goroutine 從當前線程退出,給其餘 goroutine 運行的機會。在兩次操做中間這樣作的目的是強制調度器切換兩個 goroutine,以便讓競爭條件的效果變得更明顯。線程
若是不是咱們經過調用 Gosched 函數讓競爭條件的效果變得明顯,那麼屢次運行這段程序輸出的 counter 值極可能是不同的,會是 2,3,4 中的一個值。這種狀況下致使的問題每每很是難以定位。code
和其它編程語言同樣,Golang 提供了原子函數和鎖等機制來解決同步問題。可是使用這些機制並不會使併發編程變得更簡單。接下來筆者將介紹 Golang 中提供的 channel(通道)功能,看它是如何以簡潔的方式解決同步問題的。協程
參考:
《Go語言實戰》htm