[譯] part 21: golang Goroutines

咔嘰咔嘰發表於2019-04-01

在前面的教程中,我們討論了併發以及它與並行的不同之處。在本教程中,我們將討論如何使用Goroutines在 Go 中實現併發性。

什麼是Goroutines

Goroutines是與其他函式或方法同時執行的函式或方法。 Goroutines可以被認為是輕量級執行緒。與執行緒相比,建立Goroutine的成本很小。因此,Go 應用程式通常可以輕鬆執行數千個Goroutines

Goroutines相較執行緒的優勢

  • 與執行緒相比,Goroutines非常輕量化。它們的堆疊大小隻有幾 kb,堆疊可以根據應用程式的需要而伸縮,而線上程的情況下,堆疊必須固定指定大小。
  • Goroutines被複用到較少數量的系統執行緒。程式中可能一個執行緒有數千個Goroutines。如果該執行緒中的任何Goroutine阻塞,則建立另一個系統執行緒,並將剩餘的Goroutines移動到新的執行緒。所有這些都由執行時處理,Go 從這些複雜的細節中抽象出來一個簡潔的 API 來原生支援併發。
  • Goroutines使用channel進行通訊。Goroutines使用channel通訊可以避免因訪問共享記憶體而發生競態條件。channel可以被認為是Goroutines通訊的管道。我們將在下一個教程中詳細討論channel

如何啟動Goroutines

使用關鍵字go對函式或方法呼叫進行字首修飾,就可以執行新的Goroutine了。

來建立一個Goroutine 吧:)

package main

import (
    "fmt"
)

func hello() {
    fmt.Println("Hello world goroutine")
}
func main() {
    go hello()
    fmt.Println("main function")
}
複製程式碼

Run in playgroud

在第 11 行,go hello()啟動了一個新的Goroutine。現在hello函式將與main函式一起併發執行。main在其自己的Goroutine中執行,並把main函式執行的Goroutinemain Goroutine主協程。

執行這個程式,你會有一個驚喜!

該程式僅輸出了main函式的文字。我們啟動的Goroutine怎麼了?我們需要了解go協程的兩個主要屬性,就知道為什麼會發生這種情況了。

  • 當一個新的Goroutine啟動時,Goroutine呼叫立即返回。與函式不同,控制器不會等待Goroutine完成執行。在Goroutine呼叫之後,控制器立即返回到下一行程式碼,並忽略了Goroutine的任何返回值。
  • main Goroutine控制該程式的任何其他Goroutines執行。如果main Goroutine終止,那麼程式將被終止,其他Goroutine將可能得不到執行。

我猜你能夠理解為什麼我們的Goroutine沒有被執行。在第 11 行呼叫go hello(),控制器立即執行下一行程式碼並不等待hello goroutine執行,然後列印了main function之後main Goroutine終止。沒有等待時間,因此你的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")
}
複製程式碼

Run in playgroud

在上面程式的第 13 行,我們呼叫了time包的Sleep方法,該方法讓正在執行它的go協程休眠。在這種情況下,main Goroutine進入休眠狀態 1 秒鐘。現在呼叫go hello()有足夠的時間在main Goroutine終止之前執行。該程式首先列印Hello world goroutine,然後等待 1 秒然後列印main function

這種在mian Goroutine中使用sleep等待其他Goroutines完成執行的方式只是我們用來理解Goroutines如何工作的,正常情況下肯定不能這麼做。channels可用於阻塞main Goroutine,直到所有其他Goroutines完成執行。我們將在下一個教程中討論channel

啟動多個Goroutines

再寫一個程式,啟動多個Goroutines以更好地理解它。

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")
}
複製程式碼

Run in playgroud

上面的程式在 21 和 22 行分別啟動了兩個Goroutines,這兩個Goroutines同時執行。numbers Goroutine最初睡眠 250 毫秒然後列印 1,然後再次睡眠並列印 2,並且迴圈直到它列印 5。類似地,alphabets Goroutine從 a 到 e 列印字母並且是 400 毫秒的睡眠時間。main Goroutine在啟動alphabetsnumbers Goroutines後睡眠 3000 毫秒,然後終止。

輸出,

1 a 2 3 b 4 c 5 d e main terminated
複製程式碼

下圖描繪了該程式的工作原理。請在新標籤頁中開啟圖片以獲得更好的可視性 :)

[譯] part 21: golang Goroutines

藍色影象的第一部分代表numbers Goroutine,栗色的第二部分代表alphabets Goroutine,綠色的第三部分代表main Goroutine,黑色的合併了上述三個並向我們展示如何程式如何執行的。每個框頂部的 0 ms,250 ms 等字串表示以毫秒為單位的時間,輸出在每個框的底部,例如 1, 2, 3 等等。藍色框告訴我們在 250 毫秒時列印 1,在 500 毫秒時列印 2,依此類推。黑色框底部的值為 1 a 2 3 b 4 c 5 d e 然後main終止,這也是程式的輸出。希望能通過這個圖理解該程式的工作原理。

相關文章