在上一篇中,我們討論了並發,以及並發和並行的區別。在這篇教程中我們將討論在Go中如何通過Go協程實現並發。
什麼是協程
Go協程(Goroutine)是與其他函數或方法同時啟動並執行函數或方法。可以認為Go協程是輕量級的線程。與建立線程相比,建立Go協程的成本很小。因此在Go中同時運行上千個協程是很常見的。
Go協程對比線程的優點
- 與線程相比,Go協程的開銷非常小。Go協程的堆棧大小隻有幾kb,它可以根據應用程式的需要而增長和縮小,而線程必須指定堆棧的大小,並且堆棧的大小是固定的。
- Go協程被多工到較少的OS線程。在一個程式中數千個Go協程可能只運行在一個線程中。如果該線程中的任何一個Go協程阻塞(比如等待使用者輸入),那麼Go會建立一個新的OS線程並將其餘的Go協程移動到這個新的OS線程。所有這些操作都是 runtime 來完成的,而我們程式員不必關心這些複雜的細節,只需要利用 Go 提供的簡潔的 API 來處理並發就可以了。
- Go 協程之間通過通道(channel)進行通訊。通道可以防止多個協程訪問共用記憶體時發生竟險(race condition)。通道可以想象成多個協程之間通訊的管道。我們將在下一篇教程中介紹通道。
如何建立一個協程?
在函數或方法調用之前加上關鍵字 go
,這樣便開啟了一個並發的Go協程。
讓我們建立一個協程:
package mainimport ( "fmt")func hello() { fmt.Println("Hello world goroutine")}func main() { go hello() fmt.Println("main function")}
第11行,go hello()
開啟了一個新的協程。現在 hello()
函數將和 main()
函數一起運行。main
函數在單獨的協程中運行,這個協程稱為主協程。
運行這個程式,你將得到一個驚喜。
程式僅輸出了一行文本: main function
。我們建立的協程發生了什嗎?我們需要瞭解Go協程的兩個屬性,以瞭解為什麼發生這種情況。
- 當建立一個Go協程時,建立這個Go協程的語句立即返回。與函數不同,程式流程不會等待Go協程結束再繼續執行。程式流程在開啟Go協程後立即返回並開始執行下一行代碼,忽略Go協程的任何傳回值。
- 在主協程存在時才能運行其他協程,主協程終止則程式終止,其他協程也將終止。
我想你已經知道了為什麼我們的協程為什麼沒有運行。在11行調用 go hello()
後,程式的流程直接調轉到下一條語句執行,並沒有等待 hello
協程退出,然後列印 main function
。接著主協程結束運行,不會再執行任何代碼,因此 hello
協程沒有得到啟動並執行機會。
讓我們修複這個問題:
package mainimport ( "fmt" "time")func hello() { fmt.Println("Hello world goroutine")}func main() { go hello() time.Sleep(1 * time.Second) fmt.Println("main function")}
上面的程式中,第13行,我們調用 time 包的 Sleep 函數來使調用該函數所在的協程休眠。在這裡是讓主協程休眠1秒鐘。現在調用 go hello()
有了足夠的時間得以在主協程退出之前執行。該程式首先列印 Hello world goroutine
,等待1秒鐘之後列印 main function
。
在主協程中使用 Sleep 函數等待其他協程結束的方法是不正規的,我們用在這裡只是為了說明Go協程是如何工作的。通道可以用於阻塞主協程,直到其他協程執行完畢。我們將在下一篇教程中討論通道。
開啟多個協程
讓我們寫一個程式開啟多個協程來更好的理解協程:
package mainimport ( "fmt" "time")func numbers() { for i := 1; i <= 5; i++ { time.Sleep(250 * time.Millisecond) fmt.Printf("%d ", i) }}func alphabets() { for i := 'a'; i <= 'e'; i++ { time.Sleep(400 * time.Millisecond) fmt.Printf("%c ", i) }}func main() { go numbers() go alphabets() time.Sleep(3000 * time.Millisecond) fmt.Println("main terminated")}
上面的程式在第21和22行開啟了兩個協程。現在這兩個協程同時執行。numbers
協程最初睡眠 250 毫秒,然後列印 1,接著再次睡眠然後列印2,以此類推,直到列印到 5。類似地,alphabets
協程列印從 a 到 e 的字母,每個字母之間相隔 400 毫秒。主協程開啟 numbers
和 alphabets
協程,等待 3000 毫秒,最後終止。
程式的輸出為:
1 a 2 3 b 4 c 5 d e main terminated
下面的圖片描述了這個程式是如何工作的
中,藍色的線框表示 numbers
協程,栗色的線框表示 alphabets
協程。綠色的線框表示主協程。黑色的線框合并了上述三個協程,向我們展示了該程式的工作原理。每個框頂部的 0ms,250 ms 的字串表示以毫秒為單位的時間,在每個框底部的 1,2,3 表示輸出。
藍色的線框告訴我們在 250ms
的時候列印了1,在 500ms
的時候列印了2,以此類推。因此最後一個線框底部的輸出:1 a 2 3 b 4 c 5 d e main terminated
也是整個程式的輸出。上面的映像是很好理解的,您將能夠瞭解該程式的工作原理。
本文轉自:https://blog.csdn.net/u011304970/article/details/75096323?locationNum=3&fps=1