golang併發之goroutine的基操

golang併發之goroutine的基操

996的gopher

go語言的創建goroutine很簡單隻需要一個關鍵字:go,在任何的函數前面使用go關鍵字就可以輕鬆的創建一個協程:

package main
import (
"fmt"
"time"
)
func main() {
t := []int{1, 2, 3, 4, 5, 6, 7, 8}
for _, v := range t {
go func() {
fmt.Println(v)
}()
}
time.Sleep(time.Second * 2)
}

這裡我們用go關鍵字後面寫了一個匿名函數,這樣在for循環中就會創建多個協程,這裡會創建8個,後面我們讓主協程sleep了兩秒鐘,目的是等待其他協程結束後程序再退出。

但是上面的例子大家想一下結果是什麼呢?將1-8數字全部打印出來嗎?我們看一下運行結果:

golang併發之goroutine的基操

運行結果(1)

大家可能會想這是為什麼呢?其實很簡單,就是當8個協程在打印 v 這個值的時候,v已經被賦值為8了,也就是說現在是當for循環執行結束後,這個8個協程才執行打印操作。那麼上面的寫法一定是這樣嗎?當然也不是,假如我們將t這個數組設置的多一些,例如20個或者30個元素,其中有一些協程是不一定打印數組t的最後一個元素的,如果大家不相信可以手動試一下,這裡我不再展示運行結果,希望大家能夠動起手,才能記得牢靠。

那麼我們如何將數組t中的所有元素都打印出來呢?我寫一下我認為的最好的解決辦法:

package main
import (
"fmt"
"time"
)
func main() {
t := []int{1, 2, 3, 4, 5, 6, 7, 8}
for _, v := range t {
go func(value int) {
fmt.Println(value)
}(v)
}
time.Sleep(time.Second * 2)
}

實現方式很簡單,只需要將匿名函數加上一個參數,然後將數組的每個元素當作參數傳進去就可以了,我們看一下運行結果:

golang併發之goroutine的基操

運行結果(2)

這裡我們可以看到數組的所有元素都打印出來了。所以大家剛開始接觸的時候一定要注意這個點。


我們現在來看一下這個主goroutine都做了哪些事情。當程序開始時先做一些系統的工作,然後主協程執行main函數,之後遇到go關鍵字,就會創建協程,當然不會立即創建,首先要設定每個協程能申請的棧空間的最大值,在32位系統中此最大值為250MB,64位的系統中此最大值為1GB,如果某個協程的棧空間超過此值,系統就會發起一個棧溢出的恐慌,即程序panic。

大致詳細步驟如下:

程序開始時會在當前M(MPG模型中的M)檢測系統的任務,之後主協程就會執行一些初始化的事情,檢測當前M是不是runtime.m0,如果不是說明程序有問題,就會退出,然後會創建一個defer語句,目的是在主協程退出後清理現場。之後會啟用後臺清理內存垃圾的協程,並且用GC標識,最後在執行init函數。

在遇到go關鍵字時,就會創建或者複用協程來封裝函數,這些協程會放入到相應的P的可運行G隊列中,之後就是調度器的調用過程了。

這些只是一個協程創建的一個簡單過程,大家理解之後會對go的併發有更深的理解。


後續會有更多的模式和算法以及區塊鏈相關的,如果你是想學習go語言或者是對設計模式或者算法感興趣亦或是區塊鏈開發工作者,都可以關注一下。(微信公眾號:Go語言之美,更多go語言知識信息等)。公眾號會持續為大家分享更多幹貨。


分享到:


相關文章: