剖析使Go語言高效的5個特性(4/5): Goroutines

翻譯原文連接 轉帖/轉載請註明出處編程

英文原文連接 發表於2014/06/07網絡

Goroutines

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觸發調度的位置包括:

  • Channel的發送和接收操做(當這些操做發生阻塞時)
  • Go語句,可是並不保證新的goroutine會被馬上調度
  • 像文件和網絡操做這樣的阻塞系統調用(syscalls)
  • 在垃圾回收以後

圖片描述

上面這個例子顯示了這些會發生調度的位置。

左邊是一個ReadFile函數。箭頭表示線程。當它執行到os.Open的時候,線程會被阻塞來等待文件操做。這時候調度器就會把右邊的goroutine切換到這個線程上來。一直運行到讀c chan阻塞的時候,os.Open的調用已經完成,因此調度器把線程切換回左邊的file.Read函數繼續執行。而後它被阻塞在了文件的讀寫操做上。調度器把線程又切換回右邊去運行剛纔的channel操做。這個操做如今已經不被阻塞,可是如今又要被阻塞在channel發送上了。最後當文件讀操完成的時候,線程切換回左邊繼續運行。

圖片描述

上圖顯示了底層的runtime.Syscall函數。os包裏的其它函數都會用到它。當你的代碼須要調用操做系統接口的時候,這個函數都會被調用。這裏對entersyscall的調用通知了Go的運行環境這個線程將要被阻塞。這樣,當這個線程被阻塞的時候,運行環境會建立出一個新的線程來運行其它的goroutines。

這樣的好處就是每一個Go進程只須要少許的操做系統線程。Go的運行環境來負責將可運行的goroutine分配到空閒的操做系統線程上。

相關文章
相關標籤/搜索