我認識一位很是精通golang編程技巧的工程師。他/她經驗豐富,擅長各類解決工程問題的技法,對系統瞭解也極爲深刻。遇到golang的實戰問題,他/她每每能夠一語中的,談笑間bug灰飛煙滅。javascript
這麼一位值得尊敬的工程師,在別人問他golang的goroutine是個啥玩意的時候,他/她瞠目結舌,不知道該怎麼跟對方解釋好,竟然說:「goroutine就有點像java的線程池啦。」 excuse me!這也太狗屁不通了吧!java
因此我以爲,我來裝出一副我比他/她更懂的姿態,給你們科普一下什麼是goroutine。對goroutine瞭如指掌的同窗請繞行。python
要了解啥是goroutine,咱們得先了解啥是coroutine。(不瞭解coroutine的同窗請舉起腿來!---郭德綱)c++
coroutine也就是協程。程序員
要了解什麼是協程,咱們先得了解他的特殊形式:例程。golang
一個不built in支持協程的語言,寫出來的函數咱們叫subroutine,或者叫例程。 subroutine A 調用 subroutine B 意味着在A的stack裏邊開創一片空間來做爲B的stack。B裏邊能夠繼續調用C,C裏邊能夠繼續調用D... 要注意的是,全部後面被調用的傢伙都會共享A的線程開闢的stack空間。若是咱們寫了一個調用嵌套特別複雜的函數,咱們頗有可能看見StackOverFlow! 固然若是咱們寫一個A調用B,B裏邊再調用A這樣的子子孫孫無窮盡的函數調用,咱們更容易碰到StackOverFlow!編程
例程基本講完了。bash
c/c++/java 不加上一些特殊庫的支持的話,咱們寫的函數調用方式都是把對方當作例程來的。函數
咱們能夠很容易推斷出來,在一個線程裏邊,調用者例程必定是要等到被調用的例程執行完成而且返回才能繼續執行。好比:oop
public static void funcionA(){
int resultFromB = functionB();
System.out.println("B returned : " + resultFromB);
}
複製代碼
而被調用的例程裏邊若是調用了調用者例程的話,也是從新開一個function stack來執行的。好比上面的栗子:若是functionB裏邊調用了functionA(好吧,我知道這麼寫的人是大sb),那麼另外一個functionA的stack會被建立,而後執行。
可是coroutine呢?
var q := new queue
coroutine produce
loop
while q is not full
create some new items
add the items to q
yield to consume
coroutine consume
loop
while q is not empty
remove some items from q
use the items
yield to produce
複製代碼
coroutine produce和consume可使用yield關鍵字把執行權推來推去。咱們在這個例子裏邊能夠直白的把yield理解爲:我先歇歇。
produce向q裏邊丟了東西,而後表示它要歇歇,讓consume幹會兒活。
consume用了q裏邊的東西,而後表示它要歇歇,讓produce幹會兒活。
produce和consume不是互爲subroutine,互相的stack也是獨立的。
假如produce不使用yield關鍵字,直接調用consume,那就變成了subroutine的調用了。 因此咱們說,subroutine是coroutine的特殊形式。
咱們來看看goroutine:
func main(){
ch:=make(chan int)
go routineA(ch)
go routineB(ch)
println("goroutines scheduled!")
<-ch
<-ch
}
func routineA(ch chan int){
println("A executing!")
ch<-1
}
func routineB(ch chan int){
println("B executing!")
ch<-2
}
複製代碼
go這個關鍵字很是有用!他的意思是:滾!
routineA 滾開,而後執行!
routineB 滾開,而後執行!
咱們看到,main函數這個goroutine裏邊打開了兩個新的goroutine,而且要求他們滾開去找個時間執行本身。咱們能夠斷言:"goroutines scheduled!"這行字將會先被輸出到console。而」A/B executing!「則會晚一些才輸出。 那麼問題來了,A和B啥時候才能獲得執行機會呢?
答案:當正在執行的goroutine遇到系統IO(timer,channel read/write,file read/write...)的時候,go scheduler會切換出去看看是否是有別的goroutine能夠執行一把,這個時候A和B就有機會了。實際上,這就是golang協程的概念。同時用少數的幾個線程來執行大量的goroutine協程,誰正在調用系統IO誰就歇着,讓別人用CPU。
因此若是咱們用pprof看你的服務,可能發現有幾千條goroutine,可是真正運行的線程只有小貓兩三隻。
引伸問題:假如我寫個不作任何系統IO的函數會怎麼樣?
func noIO(){
go routineA()
go routineB()
for {
println("i will never stop!")
}
}
複製代碼
go scheduler 專門對此做了處理。若是是早期的go版本,你將會看到大量的"i will never stop!",而且發現routineA和B沒啥執行機會。如今go1.9會怎麼樣,各位童鞋不放舉起腿來本身試試看。
因此綜上所述:golang裏邊使用go 這個很是關鍵的關鍵字,來觸發協程調度。 相比python等語言對協程的支持,golang的支持是很是傻瓜友好的。好比python的
yield
await
run_until_complete
複製代碼
分分鐘能夠弄暈你。
但願這篇文章能對你有點小用處。向小白介紹goroutine的時候,我以爲能夠這樣: goroutine有點像是light weight的線程。一個真正的線程能夠調度不少goroutine,不一樣的goroutine能夠被掛載在不一樣線程裏邊去執行。這些都是自動的,對程序員很友好。
題外話,咱們能夠設置系統裏邊只有一條線程,全部的goroutine都在這一條線程上面跑。那麼咱們能夠省掉一個很噁心的東西:
對的,是sync.RWMutex.