翻譯原文連接 轉帖/轉載請註明出處編程
英文原文連接 發表於2014/06/07網絡
Go語言有goroutines。它們是Go語言裏併發編程的基石。併發
首先,咱們來了解goroutines產生的歷史。在一開始,計算機只能跑一個進程。而後到了60年代,多進程或者說是分時的概念變得很流行。在一個分時系統裏,操做系統必須不停地將CPU上運行的進程進行切換。這種切換必需要將當前的進程狀態保存下來,而且將下一個進程的狀態恢復到CPU上。這個過程叫進程切換(process switching)。函數
進程切換主要有三大開銷。首先內核須要把當前進程用到的全部寄存器內容保存下來,而後把下一個進程用到的寄存器內容恢復到CPU上。內核還須要將CPU上的虛擬內存地址到物理內存地址的映射清空,由於它們只對當前進程來講是有效的。最後,還有些額外的開銷是操做系統的上下文切換(context switch),以及調度器對下一個使用CPU進程的選擇。oop
現代處理器裏有大量的寄存器。我已經沒法將它們都寫在一頁演講幻燈片上。你應該大體有概念保存和恢復它們須要多少時間裏。post
進程切換能夠在一個進程執行過程當中的任何位置發生。操做系統須要保存全部這些寄存器,由於它不知道哪些寄存器是被用到的。因而就有了線程的概念。線程從概念上來講和進程是同樣的,可是它們共享同一個內存尋址空間。spa
由於線程共享了尋址空間,它們比進程更加輕量化。因此建立和切換線程更高效。操作系統
Goroutines把這個概念作了進一步延伸。.net
Goroutines的調度是自發合做的(cooperatively),而不依賴於內核來管理它們的分時。Goroutines之間的切換隻在一些事先定義好的位置發生。在這些位置上,會有一個對Go運行環境(runtime)裏的調度器的函數調用。編譯器知道哪些寄存器被使用到了並將它們保存下來。線程
雖然goroutines的調度是自發合做的,可是調度是由運行環境完成的。Goroutines觸發調度的位置包括:
上面這個例子顯示了這些會發生調度的位置。
左邊是一個ReadFile函數。箭頭表示線程。當它執行到os.Open的時候,線程會被阻塞來等待文件操做。這時候調度器就會把右邊的goroutine切換到這個線程上來。一直運行到讀c chan阻塞的時候,os.Open的調用已經完成,因此調度器把線程切換回左邊的file.Read函數繼續執行。而後它被阻塞在了文件的讀寫操做上。調度器把線程又切換回右邊去運行剛纔的channel操做。這個操做如今已經不被阻塞,可是如今又要被阻塞在channel發送上了。最後當文件讀操完成的時候,線程切換回左邊繼續運行。
上圖顯示了底層的runtime.Syscall函數。os包裏的其它函數都會用到它。當你的代碼須要調用操做系統接口的時候,這個函數都會被調用。這裏對entersyscall的調用通知了Go的運行環境這個線程將要被阻塞。這樣,當這個線程被阻塞的時候,運行環境會建立出一個新的線程來運行其它的goroutines。
這樣的好處就是每一個Go進程只須要少許的操做系統線程。Go的運行環境來負責將可運行的goroutine分配到空閒的操做系統線程上。