作者:不喜歡夜雨天 鏈接:https://www.jianshu.com/p/23057498e2c3 來源:簡書
在go程序中,最被人所熟知的便是併發特性,一方面有goroutine這類二級線程,對這種不處於用戶態的go程的支持,另一方面便是對併發編程的簡便化,可以快捷穩定的寫出支持併發的程序。
- 先回顧進程or線程之間的通信方式 inte-process communication(IPC) 其中Go支持的IPC方法有管道、信號和socket。篇(shui)幅(ping)有限,一張圖引入回憶。
ipc圖解.jpg
- 併發和並行 簡單來講 併發就是可同時發起執行的程序,並行就是可以在支持並行的硬件上執行的併發程序;換句話說,併發程序代表了所有可以實現併發行爲的程序,這是一個比較寬泛的概念,並行程序也只是他的一個子集。 這個問題在知乎筆試中出現過,並行和併發不是一個概念。
複習過過去的基礎知識後,進入主題。
開發go程序,不管是系統性的k8s平臺,還是基於傳統的web開發,都常常使用goroutine來併發處理任務,有時候goroutine之間是相互獨立的,但是也有時候goroutine之間是需要同步和通信的;另一種情況是父goroutine需要控制屬於他的子goroutine。而goroutine的設計機制爲,goroutine退出只能由本身進行控制,不同與傳統的用戶態協程,不允許從外部強制結束該goroutine,除非goroutine奔潰或者main函數結束。目前實現多個goroutine間的同步與通信大致有:
- 全局共享變量
- channel管道通信 ---CSP模型
- context包 ---在1.7版本後引入
全局共享變量: 實現思路爲:
- 申明一個全局變量。
- 在這個全局變量作用域中,開啓多個go程,多個go程實際上是共享這個全局變量。
- 開啓的多個go程實現循環,不斷的監聽這個全局變量的情況,若全局變量屬性觸發一定條件,跳出循環,按串行順序執行到該go程結尾,go程生命週期結束。
代碼示例:
1package mainimport ( "fmt" 2 "time")func main() { 3 running := true 4 f := func() { for running { //控制的全局共享變量 5 fmt.Println("sub proc running...") 6 time.Sleep(1 * time.Second) 7 } 8 fmt.Println("sub proc exit") 9 } go f() 10 go f() 11 go f() 12 time.Sleep(2 * time.Second) 13 running = false //全局共享變量改變 14 time.Sleep(3 * time.Second) 15 fmt.Println("main proc exit") 16}
- 優點:實現簡單,不抽象,直白方便,一個變量即可簡單控制子goroutine的進行。
- 缺點:
- 不能適應結構複雜的設計,功能有限,只能適用於子go程中讀,外主程或父go程來寫全局變量,若子go程中進行寫,會出現數據同步問題,需要加鎖解決,不加鎖面對map這類線程不安全的結構會報錯。
- 還有不適合用於同級的子go程間的通信,全局變量傳遞的信息太少。
- 還有就是主進程無法等待所有子goroutine退出,因爲這種方式只能是單向通知,所以這種方法只適用於非常簡單的邏輯且併發量不太大的場景。
channel通信
首先解釋golang中的channel:channel是go中的核心部分之一,結構體簡單概括就是一個ring隊列+一個鎖 有興趣的同學可以去研究一下源碼構建。在使用中可以將channel看做管道,通過channel迸發執行的go程之間就可以發送或者接受數據,從而對併發邏輯進行控制。
go的channel的設計是建立在CSP(Communicating Sequential Process),中文可以叫做通信順序進程,是一種併發編程模型,由 Tony Hoare 於 1977 年提出。簡單來說,CSP 模型由併發執行的實體(線程或者進程)所組成,實體之間通過發送消息進行通信,這裏發送消息時使用的就是通道,或者叫 channel。CSP 模型的關鍵是關注 channel,而不關注發送消息的實體。Go 語言實現了 CSP 部分理論,goroutine 對應 CSP 中併發執行的實體,channel 也就對應着 CSP 中的 channel。 也就是說,CSP 描述這樣一種併發模型:多個Process 使用一個 Channel 進行通信, 這個 Channel 連結的 Process 通常是匿名的,消息傳遞通常是同步的(有別於 Actor Model)。
設計思路:
- 創建一個sync包中WaitGroup實例 var wg sync.WaitGroup
- 創建一個chan,負責控制go程退出
- 在每一個go程被創建前,執行註冊. wg.Add(1)
- 創建go後,在go程中監聽信號chan能否收到,使用select機制(和io多路複用相似)
- runtime主程 直接關閉chan,也可以選擇發送信號量。通知子go程結束循環,結束go程
- go程調用 wg.Done()註銷後再退出,所以在進行go程 使用defer,go程退出執行。
- wg.Wait() 在註冊的所有信息註銷後才繼續執行下一步。原理是實現一個for死循環,在註冊的值消耗完畢後,跳出死循環。
代碼解釋:
1package mainimport ( "fmt" 2 "os" 3 "os/signal" 4 "sync" 5 "syscall" 6 "time")//執行的go程方法,傳入的chanfunc consumer(stop <-chan bool) { for { 7 select { //機制類似epoll 8 case <-stop: 9 fmt.Println("exit sub goroutine") return 10 default: 11 fmt.Println("running...") 12 time.Sleep(500 * time.Millisecond) 13 } 14 } 15} 16func main() { 17 stop := make(chan bool) 18 var wg sync.WaitGroup // Spawn example consumers 19 for i := 0; i < 3; i++ { 20 wg.Add(1) go func(stop <-chan bool) { 21 defer wg.Done() 22 consumer(stop) 23 }(stop) 24 } 25 close(stop) //直接關閉chan,否則傳遞3次信號量 26 fmt.Println("stopping all jobs!") 27 wg.Wait() 28 fmt.Println("OVER") 29}
channel通信控制基於CSP模型,相比於傳統的線程與鎖併發模型,避免了大量的加鎖解鎖的性能消耗,而又比Actor模型更加靈活,使用Actor模型時,負責通訊的媒介與執行單元是緊耦合的–每個Actor都有一個信箱。而使用CSP模型,channel是第一對象,可以被獨立地創建,寫入和讀出數據,更容易進行擴展。
版權申明:內容來源網絡,版權歸原創者所有。除非無法確認,我們都會標明作者及出處,如有侵權煩請告知,我們會立即刪除並表示歉意。謝謝。