8.1. Goroutines

在Go語言中,每一個併發的執行單元叫作一個goroutine。設想這裡的一個程序有兩個函數,一個函數做計算,另一個輸出結果,假設兩個函數沒有相互之間的調用關係。一個線性的程序會先調用其中的一個函數,然後再調用另一個。如果程序中包含多個goroutine,對兩個函數的調用則可能發生在同一時刻。馬上就會看到這樣的一個程序。

如果你使用過操作系統或者其它語言提供的線程,那麼你可以簡單地把goroutine類比作一個線程,這樣你就可以寫出一些正確的程序了。goroutine和線程的本質區別會在9.8節中講。

當一個程序啟動時,其主函數即在一個單獨的goroutine中運行,我們叫它main goroutine。新的goroutine會用go語句來創建。在語法上,go語句是一個普通的函數或方法調用前加上關鍵字go。go語句會使其語句中的函數在一個新創建的goroutine中運行。而go語句本身會迅速地完成。

f()    // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait

下面的例子,main goroutine將計算菲波那契數列的第45個元素值。由於計算函數使用低效的遞歸,所以會運行相當長時間,在此期間我們想讓用戶看到一個可見的標識來表明程序依然在正常運行,所以來做一個動畫的小圖標:

gopl.io/ch8/spinner

func main() {
    go spinner(100 * time.Millisecond)
    const n = 45
    fibN := fib(n) // slow
    fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}

func spinner(delay time.Duration) {
    for {
        for _, r := range `-\|/` {
            fmt.Printf("\r%c", r)
            time.Sleep(delay)
        }
    }
}

func fib(x int) int {
    if x < 2 {
        return x
    }
    return fib(x-1) + fib(x-2)
}

動畫顯示了幾秒之後,fib(45)的調用成功地返回,並且打印結果:

Fibonacci(45) = 1134903170

然後主函數返回。主函數返回時,所有的goroutine都會被直接打斷,程序退出。除了從主函數退出或者直接終止程序之外,沒有其它的編程方法能夠讓一個goroutine來打斷另一個的執行,但是之後可以看到一種方式來實現這個目的,通過goroutine之間的通信來讓一個goroutine請求其它的goroutine,並讓被請求的goroutine自行結束執行。

留意一下這裡的兩個獨立的單元是如何進行組合的,spinning和菲波那契的計算。分別在獨立的函數中,但兩個函數會同時執行。