Go 併發 -- 協程

Seekload發表於2019-04-18

這是『就要學習 Go 語言』系列的第 21 篇分享文章

併發與並行

提到併發,相信大家還聽過另一個概念 -- 並行。我先給大家介紹下這兩者之間的區別,再來講 Go 語言的併發。

並行其實很好理解,就是同時執行的意思,在某一時間點能夠執行多個任務。 想達到並行效果,最簡單的方式就是藉助多執行緒或多程式,這樣才可在同一時刻執行多個任務。單執行緒是永遠無法達到並行狀態的。 併發是在某一時間段內可以同時處理多個任務。我們通常會說程式是併發設計的,也就是說它允許多個任務同時執行,這個同時指的就是一段時間內。單執行緒中多個任務以間隔執行實現併發。 可以說,多執行緒或多程式是並行的基礎,但單執行緒也通過協程實現了併發。

舉個常見的例子,一臺單核電腦可以下載、聽音樂,實際上這兩個任務是這樣執行的,只不過這兩個任務切換時間短,給人的感覺是同時執行的。

併發
一臺多核電腦的任務執行就是像下面這種圖顯示的一樣:
並行
可以看到,同一時刻能執行多個任務。這種任務執行方式才是真正的並行。

Go 通過協程實現併發,協程之間靠通道通訊,本篇文章先給大家介紹協程的使用,後面再寫通道。

協程

協程(Goroutine)可以理解成輕量級的執行緒,但與執行緒相比,它的開銷非常小。因此,Go 應用程式通常能併發地執行成千上萬的協程。 Go 建立一個協程非常簡單,只要在方法或函式呼叫之前加關鍵字 go 即可。

func printHello() {
	fmt.Println("hello world goroutine")
}

func main() {
	go printHello()    // 建立了協程
	fmt.Println("main goroutine")
}
複製程式碼

輸出:

main goroutine
複製程式碼

上面程式碼,第 6 行使用 go 關鍵字建立了協程,現在有兩個協程,新建立的協程和主協程。printHello() 函式將會獨立於主協程併發地執行。 是的,你沒有看錯,程式的輸出就是這樣,不信?你可以實際執行下程式。你驚訝的是,printHello() 函式為什麼沒有輸出,到底發生了什麼? 當協程建立完畢之後,主函式立即返回繼續執行下一行程式碼,不像函式呼叫,需要等函式執行完成。主協程執行完畢,程式便退出,printHello 協程隨即也退出,便不會有輸出。

修改下程式碼:

func printHello() {
	fmt.Println("hello world goroutine")
}

func main() {
	go printHello()
	time.Sleep(1*time.Second)
	fmt.Println("main goroutine")
}
複製程式碼

協程建立完成之後,main 協程先休眠 1s,預留給 printHello 協程執行的時間,所以這次輸出:

hello world goroutine
main goroutine
複製程式碼

建立多個協程

上一節就提到,可以建立多個協程,來看下例子:

func printNum() {
	for i := 1; i <= 5; i++ {
		time.Sleep(20 * time.Millisecond)
		fmt.Printf("%d ", i)
	}
}
func printChacter() {
	for i := 'a'; i <= 'e'; i++ {
		time.Sleep(40 * time.Millisecond)
		fmt.Printf("%c ", i)
	}
}
func main() {
	go printNum()
	go printChacter()
	time.Sleep(3*time.Second)
	fmt.Println("main terminated")
}
複製程式碼

上面的程式碼,除主協程之外,新建立了兩個協程:printNum 協程和 printChacter 協程。printNum 協程每隔 20 毫秒輸出 5 個數,printChacter 協程每隔 40 毫秒輸出字母。主協程建立完這兩個協程之後休眠 1s,等待其他協程執行完成。 程式輸出(你的輸出可能跟我的不一樣):

1 a 2 3 b 4 5 c d e main terminated
複製程式碼

在這裡插入圖片描述

話說回來,通過加 time.Sleep() 函式等待協程執行完成是一種“黑科技”。在實際生產環境中,不管我們是否知道其他協程執行完成需要多少時間,都不能在主協程中新增隨機睡眠呼叫等待其他協程執行完成。那怎麼辦?Go 給我們提供了通道,當協程執行完畢,能夠通知到主協程,還能夠實現協程間通訊。下節課我們來討論一下。

匿名協程

在函式那篇文章講過,存在匿名函式,通過關鍵字 go 呼叫匿名函式就是匿名協程。我們修改之前的例子:

func main() {
	go func() {
		fmt.Println("hello world goroutine")
	}()
	time.Sleep(1*time.Second)
	fmt.Println("main goroutine")
}
複製程式碼

輸出結果跟之前的一樣。

希望這篇文章給你帶來收穫,Good Day !


(全文完)

原創文章,若需轉載請註明出處!
歡迎掃碼關注公眾號「Golang來啦」或者移步 seekload.net ,檢視更多精彩文章。

給你準備了學習 Go 語言相關書籍,公號後臺回覆【電子書】領取!

公眾號二維碼

相關文章