這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go程式設計語言:支援並發、記憶體回收的編譯型系統級程式設計語言!Go語言從語言層面上就支援了並發,這與其他語言大不一樣,不像以前我們要用Thread庫 來建立線程,還要用安全執行緒的隊列庫來共用資料。
一、並發concurrency
1.基本概念
Go能處理高並發的根本原因在於執行go協程只需極少的棧記憶體(大概4~5KB),並且能根據需要動態增長和縮減佔用的資源。
Go的高並發其實是由goroutine實現的,goroutine是由官方實現的超級“線程池”。
簡單而言,goroutine就是一段代碼,一個函數入口,以及在堆上為其分配的一個堆棧。所以它非常廉價,我們可以很輕鬆的建立上萬個goroutine,但它們並不是被作業系統所調度執行,而是通過系統的線程來多路派遣這些函數的執行,使得每個用go關鍵字執行的函數可以運行成為一個單位協程。當一個協程阻塞的時候,調度器就會自動把其他協程安排到另外的線程中去執行,從而實現了程式無等待並行化運行。而且調度的開銷非常小,一顆CPU調度的規模不下於每秒百萬次,這使得在程式中能夠建立大量的goroutine,實現高並發的同時,依舊能保持高效能。
Goroutine 奉行通過通訊來共用記憶體,而不是共用記憶體來通訊。
2.區別並發和並行
這裡涉及到一個問題,很多同學搞不清楚並發與並行的區別,這裡我根據我根據知乎上這個問題某位網友的例子,我覺得很好:
- 你吃飯吃到一半,電話來了,你一直到吃完了以後才去接,這就說明你不支援並發也不支援並行
- 你吃飯吃到一半,電話來了,你停了下來接了電話,接完後繼續吃飯,這說明你支援並發
- 你吃飯吃到一半,電話來了,你一邊打電話一邊吃飯,這說明你支援並行
並發:你有處理多個任務的能力,不一定同時(一個CPU輪流)
並行:有同時處理多個任務的能力(多個CPU同時)
並發和並行都可以是很多個線程,就看這些線程能不能同時被(多個)CPU執行,可以說明是並行,並發是多個線程被一個CPU輪流切換著執行。
並發不是並行:Concurrency Is Not Parallelism
並發主要由切換時間片來實現“同時”運行,而並行則是直接利用多核實現多線程的運行,但Go可以設定使用核心數,以發揮多核電腦的能力。
並發時,由於cpu執行的太快了,不停地來回切換,讓人以為是同時進行。
並發比並行更優秀,充分的利用了CPU。
3.通道Channel
Channel 是 goroutine 溝通的橋樑,大都是阻塞同步的
- 通過
make 建立,close 關閉
- Channel 是參考型別
- 可以使用
for range 來迭代不斷操作channel
- 可以設定單向或雙向通道
- 可以設定緩衝大小,在未被填滿前不會發生阻塞
4.Select
- 可處理一個或多個channel的發送與接收
- 同時有多個可用的channel時按隨機排序處理
- 可用空的 select 來阻塞 main 函數
- 可設定逾時
二、並發樣本
1.未使用channel關鍵字
package mainimport ( "fmt" "time") func main() { go Go() // 使用go關鍵字就可執行goroutine time.Sleep(2 * time.Second) fmt.Println("Hey ...!")}func Go() { fmt.Println("Go ...!") }
列印:
➜ src go run myfirstgo/concurrency.goGo ...!Hey ...!
2.使用channel關鍵字
package mainimport ( "fmt") func main() { // 建立channel c := make(chan bool) // 執行到此處會阻塞 go func() { fmt.Println("Go ...!") c <- true }() // 將true的東西存到channel當中,然後讀出來結束運行,即通知main函數,我這裡運行完成了,可以結束了 <-c fmt.Println("Hey ...!")}
列印:
➜ src go run myfirstgo/concurrency.goGo ...!Hey ...!
3.range樣本
func main() { // 建立channel c := make(chan bool) // 執行到此處會阻塞 go func() { fmt.Println("Go ...!") c <- true close(c) }() for v := range c { fmt.Println(v) } fmt.Println("Hey ...!")}
列印:
➜ src go run myfirstgo/concurrency.goGo ...!trueHey ...!
4.多核
package mainimport ( "fmt" "runtime") // 當使用單線程執行時,會按部就班,按照順序1,2,3,4執行下去// 當使用多個CPU核心數時,任務分配是不定的,func main() { // 使用多核 runtime.GOMAXPROCS(runtime.NumCPU()) c := make(chan bool, 10) for i := 0; i < 10; i++ { go Go(c, i) } <-c}func Go(c chan bool, index int) { a := 1 for i := 0; i < 100000000; i++ { a += i } fmt.Println(index, a) if index == 9 { c <- true }}
運行多次的執行結果:
➜ src go run myfirstgo/concurrency.go3 49999999500000010 49999999500000011 49999999500000019 4999999950000001➜ src go run myfirstgo/concurrency.go2 49999999500000011 49999999500000013 49999999500000019 4999999950000001➜ src go run myfirstgo/concurrency.go9 4999999950000001➜ src go run myfirstgo/concurrency.go2 49999999500000019 4999999950000001
當使用多個CPU核心數(runtime.GOMAXPROCS)時,任務分配是不定的,所以會出現上邊的結果。
這裡有兩種解決方案:
第一種:
設定一個緩衝長度為10的channel
package mainimport ( "fmt" "runtime") // 當使用單線程執行時,會按部就班,按照順序1,2,3,4執行下去// 當使用多個CPU核心數時,任務分配是不定的,func main() { // 使用多核 runtime.GOMAXPROCS(runtime.NumCPU()) c := make(chan bool, 10) for i := 0; i < 10; i++ { go Go(c, i) } // 設定一個緩衝長度為10 的channel for i := 0; i < 10; i++ { <-c } }func Go(c chan bool, index int) { a := 1 for i := 0; i < 100000000; i++ { a += i } fmt.Println(index, a) c <- true}
列印:
➜ src go run myfirstgo/concurrency.go1 49999999500000012 49999999500000019 49999999500000013 49999999500000017 49999999500000010 49999999500000018 49999999500000016 49999999500000014 49999999500000015 4999999950000001➜ src go run myfirstgo/concurrency.go0 49999999500000019 49999999500000015 49999999500000011 49999999500000017 49999999500000012 49999999500000018 49999999500000013 49999999500000016 49999999500000014 4999999950000001
第二種:
不是通過channel解決的,而是通過sync包解決的,它有一個waitGroup,可以建立一個工作群組,可以新增工作,每完成一個任務,就標記完成Done,main函數的主要作用就是判斷是否所有的任務都完成了,如果都完成了,則退出程式。
package mainimport ( "fmt" "runtime" "sync") // 當使用單線程執行時,會按部就班,按照順序1,2,3,4執行下去// 當使用多個CPU核心數時,任務分配是不定的,func main() { // 使用多核 runtime.GOMAXPROCS(runtime.NumCPU()) // sync wg := sync.WaitGroup{} wg.Add(10) for i := 0; i < 10; i++ { go Go(&wg, i) } wg.Wait() }func Go(wg *sync.WaitGroup, index int) { a := 1 for i := 0; i < 100000000; i++ { a += i } fmt.Println(index, a) wg.Done()}
列印:
➜ src go run myfirstgo/concurrency.go1 49999999500000015 49999999500000017 49999999500000019 49999999500000016 49999999500000018 49999999500000010 49999999500000012 49999999500000013 49999999500000014 4999999950000001➜ src go run myfirstgo/concurrency.go1 49999999500000019 49999999500000012 49999999500000013 49999999500000014 49999999500000015 49999999500000010 49999999500000016 49999999500000017 49999999500000018 4999999950000001
相關文章:
golang語言並發與並行——goroutine和channel的詳細理解(一)
golang的goroutine調度機制