深入淺出Golang關鍵字"go"
1. 寫在前面
昨天,有人拿著下面這段程式碼,問題我結果為什麼不是10個10?
當然,這道題的結果,我相信很大一部分人都會弄錯。
所以,我希望做錯的人都能靜下心來看完本人的分析!ofollow,noindex">執行看結果
func main() { runtime.GOMAXPROCS(1) for i := 0; i < 10; i++ { go println(i) } runtime.Gosched() time.Sleep(time.Second) }
2. 從原始碼入手
此例中“runtime” 的問題暫且不詳說,後面會再來分析,但是,必需要明白的是runtime. GOMAXPROCS(1) 強行指定了只建立一個 “P” 來處理併發,這使得例子中的 10 個 goroutine 會是序列的。接下來,我們就來一點一點剖析關鍵字 “go”!
2.1 知其然,知其所以然
問我問題的人,其實也知道要小心的處理 for 迴圈中變數,但是他卻不瞭解哪種情況下的變數才是要真正小心行事的。我相信還有很多人會把例子和下面的程式碼理解混了:執行看結果
func main() { runtime.GOMAXPROCS(1) for i := 0; i < 10; i++ { go func() { println(i) }() } runtime.Gosched() time.Sleep(time.Second) }
結果顯示:10個10
仔細對比上述的兩段程式碼,會發現區別僅僅是 “go” 關鍵字後的函式有區別而已。那麼這一點小區別,為什麼會帶來這麼大的區別呢?我們都知道 “go” 是建立了 goroutine ,但是對於計算機而言,goroutine 只是語言封裝的語法糖而已,對於計算機依舊是識別指令及記憶體裡的值而已 。那麼 goroutine 在被建立後,留給計算機是什麼樣的記憶體佈局(資料結構)呢?
在runtime2.go:338 (type g struct ) 定義,開啟檔案後,你會看到有很多的資訊,但是,對於我們當前需要理解的是:編譯器會把 go 後面的方法和引數打包在 goroutine 裡,在上述例子中,關注如下引數:
type g struct { ... FuncVal*fnstart;// goroutine執行的函式 void*param;// 用於傳遞引數,睡眠時其它goroutine設定param,喚醒時此goroutine可以獲取 ... }
也就是說,執行到 go 的時候,編譯器就已經把 goroutine 需要執行的引數與方法都儲存了,對於 demo 1 來說就是儲存了 { println,current_i },而 demo 2 儲存的是 { main.func_xxx, nil },這裡讀者可能會注意到,第二個並沒有把 i 傳入到匿名函式中,但是引用的時候並沒有發生 panic 。為什麼?因為這裡有記憶體逃逸,這也是為什麼 demo 2 會輸出 10 個 10 的原因,本次不展開細說。只需要記住一點:編譯器會把 go 後面跟著的引數與函式都打包成了物件,等待系統排程。
2.2 為什麼??
本文說到這,其實只說了小部分,並沒有說為什麼 demo 1 的結果會與大家想象的差很多。雖說 GOMAXPROCS 為 1 導致所有的 goroutine 變成了序列。可是結果也不是大家想象的 0 ~ 9。為什麼??
其實,go 在把 goroutine 放入佇列(go sched 內容會有另外的篇幅來說明)的時候還做了一件很特別的事: proc:4799 (next ) ,程式碼內容如下:
if next { retryNext: oldnext := _p_.runnext if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) { goto retryNext } if oldnext == 0 { return } // Kick the old runnext out to the regular run queue. gp = oldnext.ptr() }
這段程式碼的意思是 go 會把每個 P 所管理的最後一個 goroutine 放入next 位置。為什麼??
這是 go 設計認為或者是有過測試:如果一個 P 的 goroutine 佇列在順序執行的時候,因為 go sched 會有很多搶佔或者排程。那麼從被執行的概率上來分析的話,放入一個 next 位置可使得每個 goroutine 的執行概率是相當的。本文不細述!
這個 next 位置也就解釋了 demo 1 的結果為什麼會是9 --- 0~8 。本文到這已經說明了前面的問題,但是,demo 中出現了
runtime.Gosched()
這一行程式碼。
如果註釋掉結果會怎樣??
如果把這一行換成runtime.Goexit() 或者os.Exit(0) 又會是如何呢??
歡迎大家嘗試並可和我討論!!!
大家可關注公眾號 cn_isnap 並留言!!