goroutine是go語言的併發體。在go語言裏面能使用go關鍵字來實現併發。算法
go func()
goroutine本質上是協程,我剛剛學習的時候就粗略地認爲goroutine是線程,直到最近纔開始搞明白goroutine的基本概念。安全
在好久之前,人們但願一個計算機(一個cpu)上能同時執行多項任務,讓cpu在某段時間內進行分片,在某段很短期內執行程序a,而後又迅速得切換到程序b去執行,讓人們看起來就像是兩個程序在同時進行,這就是併發。併發
可是人們隨之發現,cpu在切換程序的時候,若是不保存上一個程序的狀態(也就是咱們常說的context--上下文),直接切換下一個程序,就會丟失上一個程序的一系列狀態,因而引入了進程這個概念,用以劃分好程序運行時所須要的資源。所以進程就是一個程序運行時候的所須要的基本資源單位(也能夠說是程序運行的一個實體)。函數
若是一個電腦有多個cpu,每一個cpu都有進程在運行,這就是並行。學習
爲了防止用戶程序作出一些危險的指令,如關機,更改系統變量,修改別的進程數據,系統分爲兩種運行狀態,用戶態以及內核態,用戶態是咱們的程序所在的狀態,不能隨便對內核的底層進行操做。若是咱們須要使用內核的底層操做的時候,內核提供了一種調用內核的接口,咱們調用這些接口也就是系統調用,在進行系統調用的時候,cpu會切換到內核態,才能執行內核的函數。線程
人們又發現一個問題,cpu切換多個進程的時候,會花費很多的時間,由於切換進程須要切換到內核態,而每次調度須要內核態都須要讀取用戶態的數據,進程一旦多起來,cpu調度會消耗一大堆資源,所以引入了線程的概念,線程自己幾乎不佔有資源,他們共享進程裏的資源,內核調度起來不會那麼像進程切換那麼耗費資源。code
可是線程仍是須要內核去進行調度,切換起來也是須要把用戶態的數據寫入到內核態,也是須要耗費必定的計算機資源,那能夠不能夠將切換的調度改爲咱們本身控制的呢,答案是有的,協程就是把本身的調度算法交給程序(用戶態)去進行管理,能以更小的資源去進行併發。協程
goroutine就是一個協程例子,能夠根據自身調度器進行調度,當某個gooutine調用了time.sleep方法或者channel,mutex阻塞時候,調度器會使其入睡,喚醒另外一個goroutine,根本不須要進入到內核態。接口
goroutine本質上是協程,能夠理解爲不受內核調度,而受go調度器管理的線程。goroutine之間能夠經過channel進行通訊,以下:進程
func main() { c := make(chan string) go func(){ c <- "hello" }() go func(){ word := <- c + " world" fmt.Println(word) }() time.Sleep(1 * time.Second) }
goroutine只有在自身所在函數運行完畢,或者主函數運行完畢纔會打斷,於是上面的例子須要等待一秒,否則未執行完的goroutine會直接被打斷。 若是咱們併發的線程數量多了以後,咱們不可能在main裏面設置一個精確睡眠時間來評估全部的goroutine已經運行完畢而後退出。
這時候咱們可使用sync.WaitGroup來等待全部運行的goroutine運行結束後,再來退出main函數,主要原理是維護一個goroutine數量的計數器,每運行一個goroutine,計數器會加+1,運行結束後,計數器會-1,而後調用wait方法會一直阻塞,知道計數器爲0,也就是當前運行的goroutine數量爲0,實例以下:
func main() { var n sync.WaitGroup for i := 0; i < 20; i++ { n.Add(1) go func(i int, n *sync.WaitGroup) { defer n.Done() time.Sleep(1 * time.Second) fmt.Printf("goroutine %d is running\n", i) }(i, &n) } n.Wait() }