天天看點

Golang 學習路線 - Part 21:Goroutines

這裡是 Golang 教程系列的第二十一部分。

在上一教程中,我們讨論了并發及其與并行性的差別。在本教程中,我們将讨論如何使用 Goroutines 在 Go 中實作并發。

什麼是 Goroutines?

Goroutines 是與其他函數或方法并發運作的函數或方法。Goroutines 可以看作是輕量級線程。與線程相比,建立 Goroutine 的成本很小。是以,Go 應用程式通常會同時運作數千個 Goroutines。

Goroutines 相對于線程的優勢

  • 與線程相比,Goroutines 非常廉價。它們的堆棧大小隻有幾個 kb,堆棧可以根據應用程式的需要增長和收縮,而線上程的情況下,堆棧大小必須指定并固定。
  • goroutine 被多路複用到更少的 OS 線程。一個程式中可能隻有一個線程,而程式中有成千上萬個 Goroutines。如果該線程塊中的任何 Goroutine 表示等待使用者輸入,則建立另一個 OS 線程,并将其餘 Goroutine 移動到新的 OS 線程。所有這些都是由運作時處理的,作為程式員,我們從這些複雜的細節中抽象出來,并得到一個幹淨的 API 來處理并發性。
  • Goroutines使用 channels 進行通信。通過設計,channels 可以防止在使用 Goroutines 通路共享記憶體時出現競争條件。channels 可以看作是 Goroutines 通信使用的管道。我們将在下一篇教程中詳細讨論 channels。

如何開始 Goroutine?

在函數或方法調用前面加上關鍵字 go,就會得到一個并發運作的新 Goroutine。

讓我們建立一個Goroutine 😃

package main

import (  
    "fmt"
)

func hello() {  
    fmt.Println("Hello world goroutine")
}
func main() {  
    go hello()
    fmt.Println("main function")
}
           

在 playground 運作程式。

在第十一行,go hello() 啟動了一個新的 Goroutines。現在,hello() 函數已經與 main() 函數同時運作 main 函數在它自己爹 Goroutine 中運作。并稱為 “main Goroutines”

運作這個程式你會有一個驚喜

這個程式僅輸出文本

main function

我們開始的 Goroutines 怎麼了?我們需要了解 go 例程的兩個主要屬性來了解為什麼會發生這種情況

  • 當一個新的 Goroutine 啟動時,Goroutine 調用立即傳回。與函數不同,控制不會等待 Goroutine 完成執行。控制在 Goroutine 調用後立即傳回下一行代碼,并且忽略 Goroutine 的任何傳回值
  • main Goroutine 應該為其他 Goroutine 運作而運作。如果 main Goroutine 終止,則程式将終止,其他 Goroutine 将不運作

我想你現在可以了解為什麼我們的 Goroutine 沒有運作了。在第十一行中調用 go hello() 之後,控制立即傳回到下一行代碼,無需等待 hello goroutine 完成并列印 mian function。然後 mian Goroutine 終止,因為沒有其他代碼要執行,是以 hello Goroutine 沒有機會運作。

現在解決這個問題

package main

import (  
    "fmt"
    "time"
)

func hello() {  
    fmt.Println("Hello world goroutine")
}
func main() {  
    go hello()
    time.Sleep(1 * time.Second)
    fmt.Println("main function")
}
           

在 playground 運作程式。

在上面程式的第十三行中,我們調用了 time.Sleep() 方法。該方法将休眠正在執行的示例中的 go 程式。在這種情況下 main goroutine 将被休眠一秒。現在對 go hello() 的調用有足夠的時間在 mian goroutine 終止之前執行。該函數将首先列印

Hello world goroutine

然後休眠一秒列印

main function

這種在 mian Goroutine 中使用 sleep 來等待其他 Goroutine 完成執行的方法我們用來了解 Goroutine 如何工作的一種技巧。Channels 可以用來阻塞 main Goroutine,直到所有其他 Goroutine 完成它們的執行。我們将在下一篇教程中讨論 channels。

啟動多個 Goroutine

讓我們編寫一個程式來啟動多個 Goroutine,以便更好地了解 Goroutine

package main

import (  
    "fmt"
    "time"
)

func numbers() {  
    for i := 1; i <= 5; i++ {
        time.Sleep(250 * time.Millisecond)
        fmt.Printf("%d ", i)
    }
}
func alphabets() {  
    for i := 'a'; i <= 'e'; i++ {
        time.Sleep(400 * time.Millisecond)
        fmt.Printf("%c ", i)
    }
}
func main() {  
    go numbers()
    go alphabets()
    time.Sleep(3000 * time.Millisecond)
    fmt.Println("main terminated")
}
           

在 playground 運作程式。

在上面的程式中啟動了兩個 Goroutine,分别在第二十一行和第二十二行。這兩個 Goroutine 現在同時運作,numbers Goroutine 最初休眠 250 毫秒,然後列印 1,然後再次休眠并列印 2,相同的循環發生,直到列印 5。類似地,alphabets Goroutine 列印從 a到 e 的字母,睡眠時間為 400 毫秒。mian Goroutine 初始化 number 和 alphabets Goroutines,休眠 3000 毫秒,然後終止。

該程式輸出:

1 a 2 3 b 4 c 5 d e main terminated  
           

下面的圖檔描述了工作方式,請在标簽頁中打開。可以提高清晰度

Golang 學習路線 - Part 21:Goroutines

圖檔的第一部分用藍色表示 numbers Goroutine,第二部分用粉色表示 alphabets Goroutine,第三部分用綠色表示 mian Goroutine,最後一部分用黑色表示以上三部分的集合,并向我們展示程式是如何工作的。每個框頂部的字元串如 0 ms、250 ms 表示時間(以毫秒為機關),每個框底部的輸出表示為 1、2、3 等等。藍色方框表示 1 是在 250ms 之後列印的,2 是在 500ms 之後列印的,以此類推。最後一個黑色的底部有值

1 a 2 3 b 4 c 5 d e main terminated

,這也是程式的輸出。圖檔是不言自明的,你将能夠了解程式是如何工作的。

Goroutine 就是這樣,祝你有美好的一天 😃

下一個教程 - Channels