1. 程式人生 > >Go語言並發編程(一)

Go語言並發編程(一)

另一個 spa 輪換 main c語言 nbsp 成了 自動 返回

 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語言並發編程(一)