這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
在golangweekly的第36期Go Newsletter中我發現一篇短文"How Goroutines Work" ,其作者在參考了諸多資料後,簡短概要地總結了一下 Goroutine的工作原理,感覺十分適合剛入門的Gophers(深入理解Goroutine調度的話,可以參考Daniel Morsing的" The Go scheduler" )。這裡粗譯如下。
一、Go語言簡介
如果你是Go語言新手,或如果你對"並發(Concurrency)不是並行(parallelism)"這句話毫無趕腳,那麼請看一下Rob Pike大神關於這個主題的演講吧,演講共30分 鐘,我敢保證你在這個演講上花費30分鐘是絕對值得的。
總結一下兩者(Concurrency和Parallelism)的不同:"當人們聽到並發(Concurrency)這個詞時,總是會想起並行 (Parallelism),它們之間有相關性,但卻是兩個明顯不同的概念。在編程領域,並發(Concurrency)是獨立的執行過程 (Process)的組合,而並行(Parallelism)則是計算(可能是相關聯的)的同時執行。並發(Concurrency)是關於同時 應對很多事情(deal with lots of things),而並行(Parallelism)則是同時做許多事情(do lots of things)"。(Rob Pike的“Concurrency is not parallelism")
Go語言支援我們編寫並發(Concurrent)的程式。它提供了Goroutine以及更重要的在Goroutines之間通訊的能力。這裡 我們將聚焦在前者(譯註:指並發)。
二、Goroutines和Threads
Goroutine是一個簡單的模型:它是一個函數,與其他Goroutines並發執行且共用相同地址空間。Goroutines的通常用法是根據需要建立盡可 能的Groutines,成百上千甚至上萬的。這種用法對於那些習慣了使用C++或Java的程式員來講可能會有些奇怪。建立這麼多 goroutines勢必要付出不菲的代價?一個作業系統線程使用固定大小的記憶體作為它的執行棧,當線程數增多時,線程間切換的代價也是相當的 高。這也是每處理一個request就建立一個新線程的服務程式方案被詬病的原因。
不過Goroutine完全不同。它們由Go運行時初始化並調度,作業系統根本看不到Goroutine的存在。所有的goroutines都是 活著的,並且以多工形式運行於作業系統為應用程式分配的少數幾個線程上。建立一個Goroutine並不需要太多記憶體,只需要8K的棧空間 (在Go 1.3中這個Size發生了變化)。它們根據需要在堆上分配和釋放記憶體以實現自身的增長。
Go運行時負責調度Goroutines。Goroutines的調度是協作式的,而線程不是。這意味著每次一個線程發生切換,你都需要儲存/恢 複所有寄存器,包括16個通用寄存器、PC(程式計數器)、SP(棧指標)、段寄存器(segment register)、16個XMM寄存器、FP副處理器狀態、X AVX寄存器以及所有MSR等。而當另一個Goroutine被調度時,只需要儲存/恢複三個寄存器,分別是PC、SP和DX。Go調度器和任何現代操作 系統的調度器都是O(1)複雜度的,這意味著增加線程/goroutines的數量不會增加切換時間,但改變寄存器的代價是不可忽視的。
由於Goroutines的調度是協作式的,一個持續迴圈的goroutine會導致運行於同一線程上的其他goroutines“餓死”。在 Go 1.2中,這個問題或多或少可以通過在進入函數前間或地調用Go調度器來緩解一些,因此一個包含非內嵌函式調用的迴圈是可以被調度器搶佔的。
三、Goroutine阻塞
只要阻塞存在,它在OS線程中就是不受歡迎的,因為你擁有的線程數量很少。如果你發現大量線程阻塞在網路操作或是Sleep操作上,那就是問題, 需要修正。正如前面提到的那樣,Goroutine是廉價的。更關鍵地是,如果它們在網路輸入操作、Sleep操作、Channel操作或 sync包的原語操作上阻塞了,也不會導致承載其多工線程阻塞。如果一個goroutine在上述某個操作上阻塞,Go運行時會調度另外一 個goroutine。即使成千上萬的Goroutine被建立了出來,如果它們阻塞在上述的某個操作上,也不會浪費系統資源。從作業系統的視角來看,你的程式的行為就像是一個事件驅動的C程式似的。
四、最後的想法
就是這樣,Goroutines可以並發的運行。不過和其他語言一樣,組織兩個或更多goroutine同時訪問共用資源是很重要的。最好採用Channel在不同Goroutine間傳遞資料。
最後,雖然你無法直接控制Go運行時建立的線程的數量,但可以通過調用runtime.GOMAXPROCS(n)方法設定變數GOMAXPROCS來設 定使用的處理器核的數量。提高使用的處理器核心數未必能提升你的程式的效能,這取決於程式的設計。程式剖析診斷工具(profiling tool)可以用來檢查你的程式使用處理器核心數的真實情況。
2014, bigwhite. 著作權.