iT邦幫忙

2025 iThome 鐵人賽

DAY 3
1
Cloud Native

Go 語言搶票煉金術:解鎖千萬級併發下的原子交易奇蹟系列 第 3

Go 語言搶票煉金術 Day 3 - Go 的併發工具 (一):goroutine 與 WaitGroup

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20250915/20124462YARMcXUyfa.png

Go 語言搶票煉金術:Day 3 - Go 的併發工具 (一):goroutine 與 WaitGroup

上次我們證明了在沒有任何併發控制下,一個簡單的「讀取 → 修改 → 寫入」資料庫操作是多麼脆弱。

今天來檢視 Go 語言內建的併發工具。
但必須先釐清一個關鍵點:今天討論的工具,是用於解決單一應用程式內部、多個 goroutine 之間對記憶體共享的競爭問題。

這與 Day 1 中,多個獨立請求對外部資料庫的競爭,處在不同的層級。

理解這個前提後,讓我們來看看 Go 的併發工具:goroutineWaitGroup


goroutine:併發的起點

在 Go 語言中,goroutine 是一種超輕量級的並發執行緒。

它並非由作業系統管理,而是直接由 Go 的 runtime 負責調度,因此啟動成本極低。

在函數呼叫前加個 go 關鍵字,即可變成一個新的 goroutine 了。

// 位於 Day3/goroutine/main.go
func printNumbers(prefix string) {
    for i := 1; i <= 3; i++ {
        fmt.Printf("%s: %d\n", prefix, i)
        time.Sleep(100 * time.Millisecond)
    }
}

func main() {
    // 啟動三個 goroutine 同時執行
    go printNumbers("Goroutine-A")
    go printNumbers("Goroutine-B")
    go printNumbers("Goroutine-C")

    // 嚴重錯誤:使用 Sleep 等待 goroutine,極不可靠。
    time.Sleep(2 * time.Second) 
}

會看到輸出的順序會交錯出現,這證明了 goroutine 是併發執行的。

但是用了 time.Sleep 來等待 goroutine 結束,這是一個糟糕的設計

如果 printNumbers 執行時間超過 2 秒,主程式就會提前退出,導致未完成的 goroutine 被強制中止。

這引出一個重要的問題:我們如何可靠地等待一組 goroutine 全部完成?


WaitGroup:協調多個任務的完成

sync.WaitGroup 是一個計數器,用於等待一組 goroutine 全部執行完畢。

  • Add(n): 將計數器增加 n。

  • Done(): 將計數器減 1,通常在 goroutine 結束時透過 defer 呼叫。

  • Wait(): 阻塞當前執行緒,直到計數器歸零。

透過 WaitGroup,我們可以精準地協調任務,避免使用不穩定的 time.Sleep

WaitGroup 協調併發執行

// 位於 Day3/waitgroup/main.go
func printNumbersWithWG(prefix string, wg *sync.WaitGroup) {
    defer wg.Done() // 函數完成時通知 WaitGroup
    
    // 每個 worker 會做 5 次才是完成
    for i := 1; i <= 5; i++ {
        fmt.Printf("%s: %d\n", prefix, i)
        time.Sleep(100 * time.Millisecond)
    }
    fmt.Printf("%s 完成工作!\n", prefix)
}

func main() {
    var wg sync.WaitGroup

    wg.Add(3) // 我們要等待 3 個 goroutine
    go printNumbersWithWG("Worker-A", &wg)
    go printNumbersWithWG("Worker-B", &wg)
    go printNumbersWithWG("Worker-C", &wg)

    wg.Wait() // 等待所有 goroutine 完成
    fmt.Println("主程序:所有 Worker 都完成了!程序結束。")
}

執行結果:
https://ithelp.ithome.com.tw/upload/images/20250917/20124462y1ofey2cpp.png

WaitGroup 提供了一種可靠穩定方式來同步多個併發任務的完成。
當所有 worker 都呼叫了 Done()wg.Wait() 才會解除阻塞,主程式才能繼續執行。

主程式啟動多個 goroutine,並使用 WaitGroup 來等待它們全部完成。

https://ithelp.ithome.com.tw/upload/images/20250917/20124462tcNRv5klJU.png

重點摘要

  • goroutine 是 Go 併發的執行單元,用 go 關鍵字啟動,需要協調機制。
  • WaitGroup 是可靠的協調工具,用於等待一組 goroutine 完成。

只是當多個 goroutine 同時操作同一個變數時,會引發一個更棘手的問題:共享記憶體競爭
下一篇會繼續來面對這個問題。

參考資源


上一篇
Go 語言搶票煉金術 Day 2 - 併發陷阱:為什麼你的搶票系統總在超賣?
系列文
Go 語言搶票煉金術:解鎖千萬級併發下的原子交易奇蹟3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言