2012-10-28 183 views
56

a version prior to the release of go 1.5 of the Tour of Go website中,有一段代碼看起來像這樣。runtime.Gosched究竟做了什麼?

package main 

import (
    "fmt" 
    "runtime" 
) 

func say(s string) { 
    for i := 0; i < 5; i++ { 
     runtime.Gosched() 
     fmt.Println(s) 
    } 
} 

func main() { 
    go say("world") 
    say("hello") 
} 

輸出看起來是這樣的:

hello 
world 
hello 
world 
hello 
world 
hello 
world 
hello 

是什麼在困擾我的是,當runtime.Gosched()被刪除,該程序不再打印「世界」。

hello 
hello 
hello 
hello 
hello 

這是爲什麼? runtime.Gosched()如何影響執行?

回答

94

如果在不指定GOMAXPROCS環境變量的情況下運行Go程序,則會定期在單個OS線程中執行Go例程。但是,爲了讓程序看起來像是多線程的(這就是goroutines的用途,不是嗎?),Go調度器有時必須切換執行上下文,因此每個goroutine都可以完成它的工作。

正如我所說的,當沒有指定GOMAXPROCS變量時,Go運行時只允許使用一個線程,所以當goroutine執行一些常規工作(如計算或IO)時切換執行上下文是不可能的到普通的C函數)。只有在使用Go併發原語時才能切換上下文。當你打開幾個chans,或者(這是你的情況),當你明確地告訴調度器切換上下文 - 這是runtime.Gosched的用途。

因此,簡而言之,當一個例程中的執行上下文達到Gosched調用時,指示調度程序將執行切換到另一個goroutine。在你的情況下,有兩個goroutines,main(它代表程序的'main'線程)和另外一個,你用go say創建的。如果刪除Gosched調用,執行上下文將永遠不會從第一個goroutine傳輸到第2個goroutine,因此對您而言沒有「世界」。當存在Gosched時,調度程序會將每次循環迭代的執行從第一個goroutine轉移到第二個goroutine,反之亦然,所以您有'hello'和'world'交錯。我們稱之爲'合作式多任務':goroutines必須明確地將控制權交給其他goroutines。大多數現代操作系統使用的方法稱爲「搶先式多任務」:執行線程不關心控制傳輸;調度程序將執行上下文透明地切換給它們。經常使用合作的方法來實現'綠色線程',也就是邏輯併發協程,它們不會將1:1映射到操作系統線程 - 這就是Go運行時及其goroutines的實現方式。

更新

我已經提到GOMAXPROCS環境變量,但沒有說明它是什麼。現在是解決這個問題的時候了。

當此變量設置爲正數N時,Go運行時將能夠創建最多N本機線程,其上將調度所有綠色線程。本機線程是由操作系統創建的一種線程(Windows線程,pthreads等)。這意味着如果N大於1,那麼goroutines可能會安排在不同的本地線程中執行,因此可以並行運行(至少取決於您的計算機功能:如果您的系統基於多核處理器,這些線程可能會真正並行;如果您的處理器具有單核,那麼在OS線程中實現的搶先式多任務將創建並行執行的可見性)。

可以使用runtime.GOMAXPROCS()函數來設置GOMAXPROCS變量,而不是預先設置環境變量。使用這樣的事情在你的程序,而不是當前main的:

func main() { 
    runtime.GOMAXPROCS(2) 
    go say("world") 
    say("hello") 
} 

在這種情況下,你可以看到有趣的結果。您可能會得到打印錯誤交錯的「hello」和「world」行,例如

hello 
hello 
world 
hello 
world 
world 
... 

如果夠程定於單獨操作系統的線程會發生這種情況。實際上,搶先式多任務處理(或多核系統情況下的並行處理)如何實現:線程是並行的,並且它們的組合輸出是不確定的。順便說一句,你可以離開或刪除Gosched通話,它似乎沒有任何影響時,GOMAXPROCS是大於1.

以下是我所得到的與runtime.GOMAXPROCS調用的幾個運行的程序。

hyperplex /tmp % go run test.go 
hello 
hello 
hello 
world 
hello 
world 
hello 
world 
hyperplex /tmp % go run test.go 
hello 
world 
hello 
world 
hello 
world 
hello 
world 
hello 
world 
hyperplex /tmp % go run test.go 
hello 
hello 
hello 
hello 
hello 
hyperplex /tmp % go run test.go 
hello 
world 
hello 
world 
hello 
world 
hello 
world 
hello 
world 

請參閱,有時輸出很漂亮,有時不是。非決定論在行動:)

另一個更新

看起來像在圍棋編譯器Go運行時力的新版本夠程產生不僅併發原語的用法,但對OS的系統調用了。這意味着執行上下文可以在IO函數調用中在goroutine之間切換。因此,在最近的Go編譯器中,即使當GOMAXPROCS未設置或設置爲1時,也可以觀察到不確定行爲。

+0

偉大的工作!但我沒有遇到這個問題下去1.0.3,更奇怪。 – MrROY

+1

這是真的。我剛剛檢查了1.0.3版本,是的,這種行爲沒有出現:即使使用GOMAXPROCS == 1,程序的工作方式就好像GOMAXPROCS> = 2。似乎在1.0.3中調度器已經調整。 –

+0

謝謝!我希望我能兩次讚揚這個! –

3

合作計劃是罪魁禍首。如果沒有屈服,另一個(比如說「世界」)的goroutine可能合法地在零/主要終止之前執行零機會,每個規格終止所有gorutine - 即。整個過程。

+1

好吧,運行時.Gosched()會產生。那是什麼意思?它產生控制回到主要功能? –

+4

在這個特定的情況下是的。一般而言,它會要求調度程序以有意未明的選擇順序啓動並運行任何一個「就緒」的goroutine。 – zzzz