這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go routine indeed
本短結論引用自:goroutine背後的系統知識,讓我瞭解為什麼goroutine這麼輕量級,以及其優勢劣勢。
Go語言通過goroutine提供了目前為止所有(我所瞭解的)語言裡對於並發編程的最清晰最直接的支援,Go語言的文檔裡對其特性也描述的非常全面甚至超過了,在這裡,基於我們上面的系統知識介紹,列舉一下goroutine的特性,算是小結:
(1) goroutine是Go語言運行庫的功能,不是作業系統提供的功能,goroutine不是用線程實現的。具體可參見Go語言源碼裡的pkg/runtime/proc.c
(2) goroutine就是一段代碼,一個函數入口,以及在堆上為其分配的一個堆棧。所以它非常廉價,我們可以很輕鬆的建立上萬個goroutine,但它們並不是被作業系統所調度執行
(3) 除了被系統調用阻塞的線程外,Go運行庫最多會啟動$GOMAXPROCS個線程來運行goroutine
(4) goroutine是協作式調度的,如果goroutine會執行很長時間,而且不是通過等待讀取或寫入channel的資料來同步的話,就需要主動調用Gosched()來讓出CPU
(5) 和所有其他並發架構裡的協程一樣,goroutine裡所謂“無鎖”的優點只在單線程下有效,如果$GOMAXPROCS > 1並且協程間需要通訊,Go運行庫會負責加鎖保護資料,這也是為什麼sieve.go這樣的例子在多CPU多線程時反而更慢的原因
(6) Web等服務端程式要處理的請求從本質上來講是平行處理的問題,每個請求基本獨立,互不依賴,幾乎沒有資料互動,這不是一個並發編程的模型,而並發編程架構只是解決了其語義表述的複雜性,並不是從根本上提高處理的效率,也許是並發串連和並發編程的英文都是concurrent吧,很容易產生“並發編程架構和coroutine可以高效處理大量並發串連”的誤解。
(7) Go語言運行庫封裝了非同步IO,所以可以寫出貌似並發數很多的服務端,可即使我們通過調整$GOMAXPROCS來充分利用多核CPU平行處理,其效率也不如我們利用IO事件驅動設計的、按照事務類型劃分好合適比例的線程池。在回應時間上,協作式調度是硬傷。
(8) goroutine最大的價值是其實現了並發協程和實際並存執行的線程的映射以及動態擴充,隨著其運行庫的不斷髮展和完善,其效能一定會越來越好,尤其是在CPU核心數越來越多的未來,終有一天我們會為了代碼的簡潔和可維護性而放棄那一點點效能的差別。
啟動一個go routine
go關鍵字+函數名即可啟動一個go routine:
package mainimport ("fmt""time")func p() { for i := 0; i < 100; i++ { fmt.Println(i) time.Sleep(time.Second * 1) } }func main() { go p() var input string fmt.Scanln(&input) fmt.Println("End")}
goroutine通訊:Channel
go routine使用channel來進行routine間的通訊:
package mainimport ("fmt""time""math/rand")func sell(c chan int) {for {num := <- cfmt.Println("Sell ",num," bread")}}func produce(c chan int){for {num := rand.Intn(10)t := time.Duration(num)fmt.Println("Product ",num," bread")c <- numtime.Sleep(time.Second* t)}}func main() {var c chan int = make(chan int) go sell(c) go produce(c) var input string fmt.Scanln(&input) fmt.Println("End")}//輸出結果:Product 1 breadSell 1 breadProduct 7 breadSell 7 breadProduct 7 breadSell 7 breadProduct 9 breadSell 9 bread
預設channel是雙向的,在函數入口也可以定義為單向:
func sell(c <-chan int) //只能從通道取出func produce(c chan<- int) //通道只能放入
select語句用於在多個channel中選擇已經ready的通道,如:
select {case msg1 := <- c1: fmt.Println("Message 1", msg1)case msg2 := <- c2: fmt.Println("Message 2", msg2)case <- time.After(time.Second): fmt.Println("timeout")default: fmt.Println("nothing ready")}
time.After會在指定時間後建立一個匿名通道,用來進行等待逾時。如果所有channel都沒有準備妥當,則立即執行default塊。 在make channel時指定第二個參數可以建立一個緩衝通道,類似其他進階語言中的定長隊列:
c := make(chan int, 1)