1. 程式人生 > >二十二、Go基礎程式設計:併發程式設計—goroutine

二十二、Go基礎程式設計:併發程式設計—goroutine

1 goroutine是什麼

goroutine是Go並行設計的核心。goroutine說到底其實就是協程,但是它比執行緒更小,十幾個goroutine可能體現在底層就是五六個執行緒,Go語言內部幫你實現了這些goroutine之間的記憶體共享。執行goroutine只需極少的棧記憶體(大概是4~5KB),當然會根據相應的資料伸縮。也正因為如此,可同時執行成千上萬個併發任務。goroutine比thread更易用、更高效、更輕便。

2 建立goroutine

只需在函式調⽤語句前新增 go 關鍵字,就可建立併發執⾏單元。開發⼈員無需瞭解任何執⾏細節,排程器會自動將其安排到合適的系統執行緒上執行。

在併發程式設計裡,我們通常想講一個過程切分成幾塊,然後讓每個goroutine各自負責一塊工作。當一個程式啟動時,其主函式即在一個單獨的goroutine中執行,我們叫它main goroutine。新的goroutine會用go語句來建立。

示例程式碼:  

package main

import (
    "fmt"
    "time"
)

func newTask() {
    i := 0
    for {
        i++
        fmt.Printf("new goroutine: i = %d\n", i)
        time.Sleep(1 * time.Second) //延時1s
    }
}

func main() {
    //建立一個 goroutine,啟動另外一個任務
    go newTask()

    i := 0
    //main goroutine 迴圈列印
    for {
        i++
        fmt.Printf("main goroutine: i = %d\n", i)
        time.Sleep(1 * time.Second) //延時1s
    }
}

程式執行結果: 

 

3 主goroutine先退出

主goroutine退出後,其它的工作goroutine也會自動退出:

func newTask() {
    i := 0
    for {
        i++
        fmt.Printf("new goroutine: i = %d\n", i)
        time.Sleep(1 * time.Second) //延時1s
    }
}

func main() {
    //建立一個 goroutine,啟動另外一個任務
    go newTask()

    fmt.Println("main goroutine exit")
}

程式執行結果:

 

4 runtime包

4.1 Gosched

runtime.Gosched() 用於讓出CPU時間片,讓出當前goroutine的執行許可權,排程器安排其他等待的任務執行,並在下次某個時候從該位置恢復執行。

這就像跑接力賽,A跑了一會碰到程式碼runtime.Gosched() 就把接力棒交給B了,A歇著了,B繼續跑。

示例程式碼:

func main() {
    //建立一個goroutine
    go func(s string) {
        for i := 0; i < 2; i++ {
            fmt.Println(s)
        }
    }("world")

    for i := 0; i < 2; i++ {
        runtime.Gosched() //import "runtime"
        /*
            遮蔽runtime.Gosched()執行結果如下:
                hello
                hello

            沒有runtime.Gosched()執行結果如下:
                world
                world
                hello
                hello
        */
        fmt.Println("hello")
    }
}

4.2 Goexit

呼叫 runtime.Goexit() 將立即終止當前 goroutine 執⾏,排程器確保所有已註冊 defer 延遲呼叫被執行。

示例程式碼:

func main() {
    go func() {
        defer fmt.Println("A.defer")

        func() {
            defer fmt.Println("B.defer")
            runtime.Goexit() // 終止當前 goroutine, import "runtime"
            fmt.Println("B") // 不會執行
        }()

        fmt.Println("A") // 不會執行
    }() //別忘了()

    //死迴圈,目的不讓主goroutine結束
    for {
    }
}

程式執行結果: 

 

4.3 GOMAXPROCS

呼叫 runtime.GOMAXPROCS() 用來設定可以平行計算的CPU核數的最大值,並返回之前的值。

示例程式碼:

func main() {
    //n := runtime.GOMAXPROCS(1) //列印結果:111111111111111111110000000000000000000011111...
    n := runtime.GOMAXPROCS(2)     //列印結果:010101010101010101011001100101011010010100110...
    fmt.Printf("n = %d\n", n)

    for {
        go fmt.Print(0)
        fmt.Print(1)
    }
}

在第一次執行(runtime.GOMAXPROCS(1))時,最多同時只能有一個goroutine被執行。所以  會列印很多1。過了一段時間後,GO排程器會將其置為休眠,並喚醒另一個goroutine,這時候就開始列印很多0了,在列印的時候,goroutine是被排程到作業系統執行緒上的。

在第二次執行(runtime.GOMAXPROCS(2))時,我們使用了兩個CPU,所以兩個goroutine可以一起被執行,以同樣的頻率交替列印0和1。