標籤:替代 max 轉移 .com 核心數 bsp 關於 指正 依次
Go語言的特色不得不提的就是並發機制,在C語言中編寫非常繁瑣複雜的並發程式在Go語言中可以非常便捷。
這幾天寫並發測試指令碼的時候,結合代碼和其他大牛的文章學習了一下。把自己的理解寫下來。如有錯誤,請指正。
一、並發與並行
Go中並發程式主要通過goroutine和channel來實現。
這裡我想先解釋一下的是“並發”一詞,一開始把並發當做了並行,一直覺得代碼有問題,其實這兩者並不是一回事。
並發就是:兩個隊列,同時依次去訪問一個資源。而並行:兩個隊列,分別依次訪問兩個資源。
簡單來說,並發,就像一個人(cpu)喂2個孩子(程式),輪換著每人喂一口,表面上兩個孩子都在吃飯。並行,就是2個人喂2個孩子,兩個孩子也同時在吃飯。
程式碼範例
以前我們調用多個線程分別列印輸出有序的數字時,系統的線程會搶佔式地輸出, 表現出來的是亂序地輸出。而多個goroutine並發執行結果是輸出一串有序的數字接著一串有序的數字。範例程式碼:
var quit chan int = make(chan int)func loop() { for i := 0; i < 10; i++ { fmt.Printf("%d ", i) } quit <- 0}func main() { // 開兩個goroutine跑函數loop, loop函數負責列印10個數 go loop() go loop() //保證goroutine都執行完,主線程才結束 for i := 0; i < 2; i++ { <- quit }}
輸出結果:
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
但我們以前用線程實現的結果是亂序的,比如這樣的:
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
二、goroutine是如何執行的
實際上,預設Go所有的goroutines只能在一個線程裡跑,而一個goroutine並不相當於一個線程,goroutine的出現正是為了替代原來的線程概念成為最小的調度單位。(一旦運行goroutine時,先去當前線程尋找,如果線程阻塞了,則被分配到閒置線程,若無空閑線程,就建立一個線程。注意的是,當goroutine執行完畢後,線程不會被回收,而是成為了閒置線程。)
如果當前goroutine不發生阻塞,它是不會讓出CPU給其他goroutine的, 在上面的代碼執行個體中,輸出會是一個一個goroutine進行,而如果使用sleep函數,則阻塞掉了當前goroutine, 當前goroutine主動讓其他goroutine執行, 所以形成了並發。
代碼執行個體
使用goroutine想要達到真正的並行的效果也是可以的,解決方案有兩個:
1、允許Go使用多核(runtime.GOMAXPROCS)
var quit chan int = make(chan int)func loop() { for i := 0; i < 100; i++ { //為了觀察,跑多些 fmt.Printf("%d ", i) } quit <- 0}func main() { runtime.GOMAXPROCS(2) // 最多使用2個核 go loop() go loop() for i := 0; i < 2; i++ { <- quit }}
2、手動顯式調動(runtime.Gosched)
func loop() { for i := 0; i < 10; i++ { runtime.Gosched() // 顯式地讓出CPU時間給其他goroutine fmt.Printf("%d ", i) } quit <- 0}func main() { go loop() go loop() for i := 0; i < 2; i++ { <- quit }}
執行結果:
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
第二種主動讓出CPU時間的方式仍然是在單核裡跑。但手工地切換goroutine導致了看上去的“並行”。
三、runtime
runtime調度器是個很神奇的東西,但是我但願它不存在,我希望顯式調度能更為自然些,多核處理預設開啟。
關於runtime包幾個函數:
Gosched() //讓出cpu
NumCPU()//返回當前系統的CPU核心數量
GOMAXPROCS() //設定最大的可同時使用的CPU核心數
Goexit() //退出當前goroutine(但是defer語句會照常執行)
四、總結
預設所有goroutine會在一個原生線程裡跑,也就是預設只使用一個CPU核。如果當前goroutine不發生阻塞,它是不會讓出CPU時間給其他同線程的goroutines的,這是Go運行時對goroutine的調度,我們也可以使用runtime包來手工調度。
若我們開啟多核,當一個goroutine發生阻塞,Go會自動地把與該goroutine處於同一系統線程的其他goroutines轉移到另一個系統線程上去,以使這些goroutines不阻塞。從而實現並行效果。
參考自: https://studygolang.com/articles/5463
Go語言並發編程(一)