在寫程式時,你是否曾遇過這樣的問題:
- 程式跑得很慢,某些函式明明可以同時執行,卻得一個等一個?
- 想要讓多個工作並行,但又怕不同的程序互相干擾?
- 處理龐大的計算或 I/O 任務時,CPU 占用率極低,白白浪費資源?
這時候,你需要的是 並發(Concurrency)!而 Go 語言提供了一種超級簡單且高效的並發工具: Goroutine。
在 Go 語言中,並發是其核心特性之一,而 Goroutine 則是實現並發的關鍵。你可以把它想像成一個「輕量級的執行緒」,但它比傳統執行緒更高效、佔用更少資源,並且能夠輕鬆地創建成千上萬個 Goroutine。
Goroutine 是什麼?為什麼它這麼強?
Goroutine 是 Go 語言的 輕量級執行緒(Lightweight Thread),讓你可以輕鬆執行並發程式,而不需要手動管理繁瑣的系統執行緒。
- 輕量級:相比於傳統的作業系統執行緒(Thread),Goroutine 的記憶體開銷極低。
- 執行簡單:只要在函式前加上
go
,就能讓它跑在背景,完全不需要設定任何 Thread! - 獨立運行:Goroutine 之間互不影響,即使某個 Goroutine 崩潰,也不會導致其他 Goroutine 失敗。
- 高效:Go 運行時(runtime)會自動管理 Goroutine 的調度,避免過度依賴作業系統的執行緒管理。
1 | package main |
輸出
1 | Main 函式結束 |
go hello()
會在背景執行,而main()
函式會繼續往下執行。- 這樣我們的程式就可以同時進行兩個工作,提升效率!
⚠ 但這裡有個問題—— Goroutine 是異步執行的,main() 可能在 hello() 結束前就已執行完畢,因此 time.Sleep() 讓主執行緒暫停一下,確保 Goroutine 執行。
main 函式結束後,所有 Goroutine 也會立即結束,這就引出了下一個重要工具: WaitGroup。
WaitGroup:讓主程式等 Goroutine 完成,確保 Goroutine 全部完成
當我們開啟多個 Goroutine 時,main()
可能還沒等 Goroutine 執行完就結束了,導致 Goroutine 被強制終止。為了解決這個問題,我們可以使用 sync.WaitGroup。
1 | package main |
wg.Add(2)
告訴 WaitGroup 需要等 2 個 Goroutine。 <- 增加計數器wg.Done()
會在 Goroutine 執行結束時通知 WaitGroup。 <- 減少計數器wg.Wait()
讓main()
等待所有 Goroutine 結束後才繼續。 <- 等待計數器歸零
這樣就能確保主程式不會提前結束!
我們再看一個範例:
1 | package main |
Channel:Goroutine 之間的溝通管道
Goroutine 之間如何傳遞資料呢?我們可以使用 Channel,它就像是一條輸送帶,負責在 Goroutine 之間傳遞訊息,確保 Goroutine 之間安全地交換資料!
Channel 的基本語法
1 | package main |
輸出:
1 | Hello from Goroutine! |
另一種寫法(匿名函式)
1 | package main |
ch := make(chan string)
建立了一個string
類型的 Channel。ch <- "Hello"
讓 Goroutine 發送訊息。<-ch
讓主程式接收訊息。
這兩種寫法的主要差異在於:
- 第一種寫法:將
sendMessage
定義為一個獨立函式,這樣的方式有助於程式的結構化,使程式碼更具可讀性和可維護性。 - 第二種寫法:使用匿名函式(
go func() {}
)直接啟動 Goroutine,這種方式適合用於簡單的情境,例如臨時的併發操作,而不需要額外定義函式。
在選擇哪種方式時,通常如果該 Goroutine 需要重複使用,則應該定義成獨立函式;如果只是一次性操作,則可以使用匿名函式。
Buffered Channel(有緩衝的 Channel)
Buffered channel 允許 goroutine 發送多個訊息而不會馬上被接收:
1 | package main |
這樣的 Channel 允許存入 3 個數據而不會阻塞 Goroutine。
緩衝區滿了後,goroutine 會阻塞直到有空間可用。
Mutex:避免 Goroutine 同時修改資料
當多個 Goroutine 需要共享同一個變數時,可能會發生 競爭條件(Race Condition),導致不一致的結果。這時候就需要 Mutex(互斥鎖) 來確保資料安全。
1 | package main |
increment()
用於增加計數器,使用 mutex.Lock()
和 mutex.Unlock()
保護 counter
變數。main()
啟動多個 Goroutine,每個 Goroutine 都會調用 increment()
。使用 sync.Mutex
可以確保 counter
的值正確。
lock.Lock()
確保只有一個 Goroutine 能修改counter
。lock.Unlock()
讓其他 Goroutine 繼續執行。
這樣就能保證並發環境下數據的一致性!
結論
Goroutine 讓 Go 的並發變得輕鬆簡單,但為了讓多個 Goroutine 之間能夠順利協作,我們需要:
- WaitGroup 讓主程式等 Goroutine 執行完。
- Channel 讓 Goroutine 之間安全地交換數據。
- Mutex 避免多個 Goroutine 同時修改共享變數。