不知道你有沒有注意到一個現象,仍是這段代碼,若是我跑在兩個goroutines裏面的話:併發
var quit chan int = make(chan int) func loop() { for i := 0; i < 10; i++ { fmt.Printf("%d ", i) } quit <- 0 } func main() { // 開兩個goroutine跑函數loop, loop函數負責打印10個數 go loop() go loop() for i := 0; i < 2; i++ { <- quit } }
咱們觀察下輸出:函數
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
這是否是有什麼問題??oop
之前咱們用線程去作相似任務的時候,系統的線程會搶佔式地輸出, 表現出來的是亂序地輸出。而goroutine爲何是這樣輸出的呢?測試
咱們找個例子測試下:ui
package main import "fmt" import "time" var quit chan int func foo(id int) { fmt.Println(id) time.Sleep(time.Second) // 停頓一秒 quit <- 0 // 發消息:我執行完啦! } func main() { count := 1000 quit = make(chan int, count) // 緩衝1000個數據 for i := 0; i < count; i++ { //開1000個goroutine go foo(i) } for i :=0 ; i < count; i++ { // 等待全部完成消息發送完畢。 <- quit } }
讓咱們跑一下這個程序(之因此先編譯再運行,是爲了讓程序跑的儘可能快,測試結果更好):spa
go build test.go time ./test ./test 0.01s user 0.01s system 1% cpu 1.016 total
咱們看到,總計用時接近一秒。 貌似並行了!.net
咱們須要首先考慮下什麼是併發, 什麼是並行線程
從概念上講,併發和並行是不一樣的, 簡單來講看這個圖片code
那麼回到一開始的疑問上,從上面的兩個例子執行後的表現來看,多個goroutine跑loop函數會挨個goroutine去進行,而sleep則是一塊兒執行的。blog
這是爲何?
默認地, Go全部的goroutines只能在一個線程裏跑 。
也就是說, 以上兩個代碼都不是並行的,可是都是是併發的。
若是當前goroutine不發生阻塞,它是不會讓出CPU給其餘goroutine的, 因此例子一中的輸出會是一個一個goroutine進行的,而sleep函數則阻塞掉了 當前goroutine, 當前goroutine主動讓其餘goroutine執行, 因此造成了邏輯上的並行, 也就是併發。
爲了達到真正的並行,咱們須要告訴Go咱們容許同時最多使用多個核。
回到起初的例子,咱們設置最大開2個原生線程, 咱們須要用到runtime包(runtime包是goroutine的調度器):
import ( "fmt" "runtime" ) var quit chan int = make(chan int) func loop() { for i := 0; i < 100; i++ { //爲了觀察,跑多些 fmt.Printf("%d ", i) } quit <- 0 } func main() { runtime.GOMAXPROCS(2) // 最多使用2個核 go loop() go loop() for i := 0; i < 2; i++ { <- quit } }
這下會看到兩個goroutine會搶佔式地輸出數據了。
咱們還能夠這樣顯式地讓出CPU時間:
func loop() { for i := 0; i < 10; i++ { runtime.Gosched() // 顯式地讓出CPU時間給其餘goroutine fmt.Printf("%d ", i) } quit <- 0 } func main() { go loop() go loop() for i := 0; i < 2; i++ { <- quit } }
觀察下結果會看到這樣有規律的輸出:
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
其實,這種主動讓出CPU時間的方式仍然是在單核裏跑。但手工地切換goroutine致使了看上去的「並行」。
runtime調度器是個很神奇的東西,可是我真是希望它不存在,我但願顯式調度能更爲天然些,多核處理默認開啓。
關於runtime包幾個函數:
Gosched
讓出cpu
NumCPU
返回當前系統的CPU核數量
GOMAXPROCS
設置最大的可同時使用的CPU核數
Goexit
退出當前goroutine(可是defer語句會照常執行)
咱們從例子中能夠看到,默認的, 全部goroutine會在一個原生線程裏跑,也就是隻使用了一個CPU核。
在同一個原生線程裏,若是當前goroutine不發生阻塞,它是不會讓出CPU時間給其餘同線程的goroutines的,這是Go運行時對goroutine的調度,咱們也可使用runtime包來手工調度。
本文開頭的兩個例子都是限制在單核CPU裏執行的,全部的goroutines跑在一個線程裏面,分析以下: