24. 理解 Go 語言中的協程:goroutine

Hi,大家好,我是明哥。

在自己學習 Golang 的這段時間裏,我寫了詳細的學習筆記放在我的個人微信公衆號 《Go編程時光》,對於 Go 語言,我也算是個初學者,因此寫的東西應該會比較適合剛接觸的同學,如果你也是剛學習 Go 語言,不防關注一下,一起學習,一起成長。

我的在線博客:http://golang.iswbm.com
我的 Github:github.com/iswbm/GolangCodingTime


說到Go語言,很多沒接觸過它的人,對它的第一印象,一定是它從語言層面天生支持併發,非常方便,讓開發者能快速寫出高性能且易於理解的程序。

在 Python (爲Py爲例,主要是我比較熟悉,其他主流編程語言也類似)中,併發編程的門檻並不低,你要學習多進程,多線程,還要掌握各種支持併發的庫 asyncio,aiohttp 等,同時你還要清楚它們之間的區別及優缺點,懂得在不同的場景選擇不同的併發模式。

而 Golang 作爲一門現代化的編程語言,它不需要你直面這些複雜的問題。在 Golang 裏,你不需要學習如何創建進程池/線程池,也不需要知道什麼情況下使用多線程,什麼時候使用多進程。因爲你沒得選,也不需要選,它原生提供的 goroutine (也即協程)已經足夠優秀,能夠自動幫你處理好所有的事情,而你要做的只是執行它,就這麼簡單。

一個 goroutine 本身就是一個函數,當你直接調用時,它就是一個普通函數,如果你在調用前加一個關鍵字 go ,那你就開啓了一個 goroutine。

// 執行一個函數
func()

// 開啓一個協程執行這個函數
go func()

1. 協程的初步使用

一個 Go 程序的入口通常是 main 函數,程序啓動後,main 函數最先運行,我們稱之爲 main goroutine

在 main 中或者其下調用的代碼中纔可以使用 go + func() 的方法來啓動協程。

main 的地位相當於主線程,當 main 函數執行完成後,這個線程也就終結了,其下的運行着的所有協程也不管代碼是不是還在跑,也得乖乖退出。

因此如下這段代碼運行完,只會輸出 hello, world ,而不會輸出hello, go(因爲協程的創建需要時間,當 hello, world打印後,協程還沒來得及並執行)

import "fmt"

func mytest() {
	fmt.Println("hello, go")
}

func main() {
    // 啓動一個協程
	go mytest()
	fmt.Println("hello, world")
}

對於剛學習Go的協程同學來說,可以使用 time.Sleep 來使 main 阻塞,使其他協程能夠有機會運行完全,但你要注意的是,這並不是推薦的方式(後續我們會學習其他更優雅的方式)。

當我在代碼中加入一行 time.Sleep 輸出就符合預期了。

import (
	"fmt"
	"time"
)

func mytest() {
	fmt.Println("hello, go")
}

func main() {
	go mytest()
	fmt.Println("hello, world")
	time.Sleep(time.Second)
}

輸出如下

hello, world
hello, go

2. 多個協程的效果

爲了讓你看到併發的效果,這裏舉個最簡單的例子

import (
	"fmt"
	"time"
)

func mygo(name string) {
	for i := 0; i < 10; i++ {
		fmt.Printf("In goroutine %s\n", name)
        // 爲了避免第一個協程執行過快,觀察不到併發的效果,加個休眠
		time.Sleep(10 * time.Millisecond) 
	}
}

func main() {
	go mygo("協程1號") // 第一個協程
	go mygo("協程2號") // 第二個協程
	time.Sleep(time.Second)
}

輸出如下,可以觀察到兩個協程就如兩個線程一樣,併發執行

In goroutine 協程2號
In goroutine 協程1號
In goroutine 協程1號
In goroutine 協程2號
In goroutine 協程2號
In goroutine 協程1號
In goroutine 協程1號
In goroutine 協程2號
In goroutine 協程1號
In goroutine 協程2號
In goroutine 協程1號
In goroutine 協程2號
In goroutine 協程1號
In goroutine 協程2號
In goroutine 協程1號
In goroutine 協程2號
In goroutine 協程1號
In goroutine 協程2號
In goroutine 協程1號
In goroutine 協程2號

通過以上簡單的例子,是不是折服於Go的這種強大的併發特性,將同步代碼轉爲異步代碼,真的只要一個關鍵字就可以了,也不需要使用其他庫,簡單方便。

本篇只介紹了協程的簡單使用,真正的併發程序還是要結合 信道 (channel)來實現。關於信道的內容,將在下一篇文章中介紹。

系列導讀

01. 開發環境的搭建(Goland & VS Code)

02. 學習五種變量創建的方法

03. 詳解數據類型:****整形與浮點型

04. 詳解數據類型:byte、rune與string

05. 詳解數據類型:數組與切片

06. 詳解數據類型:字典與布爾類型

07. 詳解數據類型:指針

08. 面向對象編程:結構體與繼承

09. 一篇文章理解 Go 裏的函數

10. Go語言流程控制:if-else 條件語句

11. Go語言流程控制:switch-case 選擇語句

12. Go語言流程控制:for 循環語句

13. Go語言流程控制:goto 無條件跳轉

14. Go語言流程控制:defer 延遲調用

15. 面向對象編程:接口與多態

16. 關鍵字:make 和 new 的區別?

17. 一篇文章理解 Go 裏的語句塊與作用域

18. 學習 Go 協程:goroutine

19. 學習 Go 協程:詳解信道/通道

20. 幾個信道死鎖經典錯誤案例詳解

21. 學習 Go 協程:WaitGroup

22. 學習 Go 協程:互斥鎖和讀寫鎖

23. Go 裏的異常處理:panic 和 recover

24. 超詳細解讀 Go Modules 前世今生及入門使用

25. Go 語言中關於包導入必學的 8 個知識點

26. 如何開源自己寫的模塊給別人用?

27. 說說 Go 語言中的類型斷言?

28. 這五點帶你理解Go語言的select用法


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章