翻譯原文鏈接? ?轉(zhuǎn)帖/轉(zhuǎn)載請(qǐng)注明出處
英文原文鏈接? ?發(fā)表于2014/06/07
Goroutines
Go語(yǔ)言有g(shù)oroutines。它們是Go語(yǔ)言里并發(fā)編程的基石。
首先,我們來(lái)了解goroutines產(chǎn)生的歷史。在一開始,計(jì)算機(jī)只能跑一個(gè)進(jìn)程。然后到了60年代,多進(jìn)程或者說是分時(shí)的概念變得很流行。在一個(gè)分時(shí)系統(tǒng)里,操作系統(tǒng)必須不停地將CPU上運(yùn)行的進(jìn)程進(jìn)行切換。這種切換必須要將當(dāng)前的進(jìn)程狀態(tài)保存下來(lái),并且將下一個(gè)進(jìn)程的狀態(tài)恢復(fù)到CPU上。這個(gè)過程叫進(jìn)程切換(process switching)。

進(jìn)程切換主要有三大開銷。首先內(nèi)核需要把當(dāng)前進(jìn)程用到的所有寄存器內(nèi)容保存下來(lái),然后把下一個(gè)進(jìn)程用到的寄存器內(nèi)容恢復(fù)到CPU上。內(nèi)核還需要將CPU上的虛擬內(nèi)存地址到物理內(nèi)存地址的映射清空,因?yàn)樗鼈冎粚?duì)當(dāng)前進(jìn)程來(lái)說是有效的。最后,還有些額外的開銷是操作系統(tǒng)的上下文切換(context switch),以及調(diào)度器對(duì)下一個(gè)使用CPU進(jìn)程的選擇。

現(xiàn)代處理器里有大量的寄存器。我已經(jīng)無(wú)法將它們都寫在一頁(yè)演講幻燈片上。你應(yīng)該大致有概念保存和恢復(fù)它們需要多少時(shí)間里。
進(jìn)程切換可以在一個(gè)進(jìn)程執(zhí)行過程中的任何位置發(fā)生。操作系統(tǒng)需要保存所有這些寄存器,因?yàn)樗恢滥男┘拇嫫魇潜挥玫降?。于是就有了線程的概念。線程從概念上來(lái)說和進(jìn)程是一樣的,但是它們共享同一個(gè)內(nèi)存尋址空間。

因?yàn)榫€程共享了尋址空間,它們比進(jìn)程更加輕量化。所以創(chuàng)建和切換線程更高效。
Goroutines把這個(gè)概念做了進(jìn)一步延伸。

Goroutines的調(diào)度是自發(fā)合作的(cooperatively),而不依賴于內(nèi)核來(lái)管理它們的分時(shí)。Goroutines之間的切換只在一些事先定義好的位置發(fā)生。在這些位置上,會(huì)有一個(gè)對(duì)Go運(yùn)行環(huán)境(runtime)里的調(diào)度器的函數(shù)調(diào)用。編譯器知道哪些寄存器被使用到了并將它們保存下來(lái)。

雖然goroutines的調(diào)度是自發(fā)合作的,但是調(diào)度是由運(yùn)行環(huán)境完成的。Goroutines觸發(fā)調(diào)度的位置包括:
- Channel的發(fā)送和接收操作(當(dāng)這些操作發(fā)生阻塞時(shí))
- Go語(yǔ)句,但是并不保證新的goroutine會(huì)被立刻調(diào)度
- 像文件和網(wǎng)絡(luò)操作這樣的阻塞系統(tǒng)調(diào)用(syscalls)
- 在垃圾回收之后

上面這個(gè)例子顯示了這些會(huì)發(fā)生調(diào)度的位置。
左邊是一個(gè)ReadFile函數(shù)。箭頭表示線程。當(dāng)它執(zhí)行到os.Open的時(shí)候,線程會(huì)被阻塞來(lái)等待文件操作。這時(shí)候調(diào)度器就會(huì)把右邊的goroutine切換到這個(gè)線程上來(lái)。一直運(yùn)行到讀c chan阻塞的時(shí)候,os.Open的調(diào)用已經(jīng)完成,所以調(diào)度器把線程切換回左邊的file.Read函數(shù)繼續(xù)執(zhí)行。然后它被阻塞在了文件的讀寫操作上。調(diào)度器把線程又切換回右邊去運(yùn)行剛才的channel操作。這個(gè)操作現(xiàn)在已經(jīng)不被阻塞,但是現(xiàn)在又要被阻塞在channel發(fā)送上了。最后當(dāng)文件讀操完成的時(shí)候,線程切換回左邊繼續(xù)運(yùn)行。

上圖顯示了底層的runtime.Syscall函數(shù)。os包里的其它函數(shù)都會(huì)用到它。當(dāng)你的代碼需要調(diào)用操作系統(tǒng)接口的時(shí)候,這個(gè)函數(shù)都會(huì)被調(diào)用。這里對(duì)entersyscall的調(diào)用通知了Go的運(yùn)行環(huán)境這個(gè)線程將要被阻塞。這樣,當(dāng)這個(gè)線程被阻塞的時(shí)候,運(yùn)行環(huán)境會(huì)創(chuàng)建出一個(gè)新的線程來(lái)運(yùn)行其它的goroutines。
這樣的好處就是每個(gè)Go進(jìn)程只需要少量的操作系統(tǒng)線程。Go的運(yùn)行環(huán)境來(lái)負(fù)責(zé)將可運(yùn)行的goroutine分配到空閑的操作系統(tǒng)線程上。