Golang教程:(二十一)協程
在上一篇教程中,我們討論了併發,以及併發和並行的區別。在這篇教程中我們將討論在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 main
import (
"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 main
import (
"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 main
import (
"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
也是整個程式的輸出。上面的影象是很好理解的,您將能夠了解該程式的工作原理。
Go協程的介紹就到這裡。祝你有美好的一天!