2023-09-11|閱讀時間 ‧ 約 8 分鐘

探索Goroutine:Go語言的併發魔法

raw-image

👨‍💻簡介

在日常生活中,如果能同時做很多事情,效率肯定大大提升,那麼在Go語言中,該如何做到呢,答案就是今天的主角Goroutine了,在Go語言中,讓併發變得簡單的強大工具,今天就是來給他一個快速介紹。

什麼是Goroutine?

首先,讓我們以一個簡單的方式來解釋什麼是Goroutine。Goroutine是Go語言的一個特別的功能,它就像是小型的工作任務,可以讓我們同時處理很多事情,而不需要浪費太多電腦資源。可以把它想像成比傳統方式更聰明的方式來處理多項工作,而不會讓電腦變得超級忙碌。這種功能讓Go語言在處理大量同時執行的工作時變得非常強大。

如何創建和啟動Goroutine

要使用Goroutine很簡單,只需要創建一個函數,然後使用go關鍵字在要使用goroutine的函數前面就完成了。可以看以下範例:

package main

import (
"fmt"
)
func main() {
go sayHello()
// 主程式不會等待Goroutine完成
}
func sayHello() {
fmt.Println("Hello, Goroutine!")
}

在這個例子中,我們使用go關鍵字啟動一個新的Goroutine,Goroutine執行了sayHello函數。但須注意,主程式main不會等待Goroutine完成,所以可能不會看到有印出任何東西來。

可以稍微讓main主程式睡一下,就可以看到輸出了

package main

import (
"fmt"
"time"
)
func main() {
go sayHello()
time.Sleep(1)
}
func sayHello() {
fmt.Println("Hello, Goroutine!")
}

Goroutine的執行過程

下面提供了一個簡單的小程式,在主程式裡有兩個goroutine,試著執行一下程式可以發現輸出是一段一段的,會是0跟1交錯印出,代表兩邊的goroutine會搶著印出,呈現競爭狀態

package main

import (
"fmt"
"time"
)
func main() {
go print1()
go print2()
time.Sleep(time.Second)
}
func print1() {
for i := 0; i < 100; i++ {
fmt.Print("0")
}
}
func print2() {
for i := 0; i < 100; i++ {
fmt.Print("1")
}
}

Goroutine之間的通信

併發往往涉及到多個任務之間的通信。這就是通道(Channel)的用武之地。通道是一種特殊的資料結構,用於在不同的Goroutine之間傳遞資料:

package main

import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go sendData(ch)
go receiveData(ch)
time.Sleep(2 * time.Second) // 等待Goroutines完成
}
func sendData(ch chan string) {
ch <- "Hello from Goroutine!"
}
func receiveData(ch chan string) {
msg := <-ch
fmt.Println(msg)
}

在這個例子中,我們先創建了一個通道ch,然後使用通道在兩個Goroutine之間傳遞消息。 通道的溝通可以看到範例,<- chan 代表將資料從channel中取出,而chan <- 則代表將資料放進channel

Goroutine同步與等待

通常情況下,我們希望主程式能夠等待所有的Goroutine完成,以確保結果的完整性。這就是WaitGroup的作用:

sync.WaitGroup

這個函數的主要功能是讓主程式等待所有的Goroutine完成,然後再繼續執行接下來的程式,主要有以下幾種方法

  • Add(delta int):用來增加計數器的值,表示有多少個Goroutine需要等待。
  • Done():用來減少計數器的值,表示一個Goroutine已經完成。通常在Goroutine執行完後使用 Done
  • Wait():用來將計數器歸零。當計數器的值為零時,Wait 函數會返回,並允許主程式繼續執行。

接著讓我們來看一下範例:

package main

import (
"fmt"
"sync"
)
func main() {
// 創建一個WaitGroup
var wg sync.WaitGroup
// 啟動5個Goroutine
for i := 0; i < 5; i++ {
wg.Add(1) // 增加計數器
go worker(i, &wg)
}
wg.Wait() // 等待所有Goroutines完成
fmt.Println("All Goroutines have finished.")
}
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 減少計數器
fmt.Printf("Worker %d is done\n", id)
}

可以看到,我們使用WaitGroup來等待所有的Goroutine完成,確保我的主程式可以完整執行而不會提前退出。

Goroutine 的特性與限制

特性

  1. 資源消耗極低:Goroutine 的創建相對輕量,主要消耗少量stack空間。這意味著你可以創建大量的 goroutine 而不必擔心資源耗盡的問題。
  2. 有效的線程管理:當一個 goroutine 被阻塞時,相應的管理線程將被擱置,但運行時會將其他 goroutine 分配給這個線程,使其繼續執行其他工作。這種機制確保了線程的高效使用,避免了資源浪費。
  3. 最大線程數限制:你可以透過設定 $GOMAXPROCS 來限制系統中的線程數量,確保它們不會無節制地增加。這有助於避免系統資源的過度消耗。

限制

  1. Goroutine 數量限制:理論上,Go 語言可以創建極多的 Goroutine,但實際上,系統的可用資源(記憶體和 CPU)是有限的。因此,你需要謹慎控制 Goroutine 的數量,以避免過多的併發造成資源耗盡或性能下降。
  2. 競爭條件和死鎖:Goroutine 的併發操作需要謹慎處理共享資源,否則可能出現競爭條件(race condition)和死鎖(deadlock)。這不是直接的限制,但是在 Goroutine 的設計和使用中需要特別注意,以確保程式的正確性。

小結

相信學習Go語言的各位,也是被Goroutine強大的併發能力所吸引,Goroutine還有許多細節,今天這篇只是簡單介紹Goroutine,之後會再慢慢深入探討Goroutine的奧妙之處。

📚Reference

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.