Golang學習筆記:goroutine

1.goroutine

goroutine是go語言的併發體。在go語言裏面能使用go關鍵字來實現併發。算法

go func()

1.1 概念介紹

goroutine本質上是協程,我剛剛學習的時候就粗略地認爲goroutine是線程,直到最近纔開始搞明白goroutine的基本概念。安全

併發

在好久之前,人們但願一個計算機(一個cpu)上能同時執行多項任務,讓cpu在某段時間內進行分片,在某段很短期內執行程序a,而後又迅速得切換到程序b去執行,讓人們看起來就像是兩個程序在同時進行,這就是併發併發

進程

可是人們隨之發現,cpu在切換程序的時候,若是不保存上一個程序的狀態(也就是咱們常說的context--上下文),直接切換下一個程序,就會丟失上一個程序的一系列狀態,因而引入了進程這個概念,用以劃分好程序運行時所須要的資源。所以進程就是一個程序運行時候的所須要的基本資源單位(也能夠說是程序運行的一個實體)。函數

並行

若是一個電腦有多個cpu,每一個cpu都有進程在運行,這就是並行。學習

用戶態與內核態

爲了防止用戶程序作出一些危險的指令,如關機,更改系統變量,修改別的進程數據,系統分爲兩種運行狀態,用戶態以及內核態,用戶態是咱們的程序所在的狀態,不能隨便對內核的底層進行操做。若是咱們須要使用內核的底層操做的時候,內核提供了一種調用內核的接口,咱們調用這些接口也就是系統調用,在進行系統調用的時候,cpu會切換到內核態,才能執行內核的函數。線程

線程

人們又發現一個問題,cpu切換多個進程的時候,會花費很多的時間,由於切換進程須要切換到內核態,而每次調度須要內核態都須要讀取用戶態的數據,進程一旦多起來,cpu調度會消耗一大堆資源,所以引入了線程的概念,線程自己幾乎不佔有資源,他們共享進程裏的資源,內核調度起來不會那麼像進程切換那麼耗費資源。code

協程

可是線程仍是須要內核去進行調度,切換起來也是須要把用戶態的數據寫入到內核態,也是須要耗費必定的計算機資源,那能夠不能夠將切換的調度改爲咱們本身控制的呢,答案是有的,協程就是把本身的調度算法交給程序(用戶態)去進行管理,能以更小的資源去進行併發。協程

goruntine

goroutine就是一個協程例子,能夠根據自身調度器進行調度,當某個gooutine調用了time.sleep方法或者channel,mutex阻塞時候,調度器會使其入睡,喚醒另外一個goroutine,根本不須要進入到內核態。接口

2.通訊

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)
}

3.安全退出

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()
}
相關文章
相關標籤/搜索