最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang中sync.WaitGroup 等待組的實(shí)現(xiàn)

 更新時(shí)間:2026年07月19日 10:59:53   作者:FfHUCisI  
本文介紹了Go語(yǔ)言中sync.WaitGroup的使用方法和注意事項(xiàng),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

一、WaitGroup 解決什么問(wèn)題

當(dāng)我們啟動(dòng)了一組 goroutine 去干活,主 goroutine 需要在所有子 goroutine 完成之后再繼續(xù)執(zhí)行。比如:

  • 并發(fā)抓取 10 個(gè)網(wǎng)頁(yè),全部抓完再匯總結(jié)果
  • 批量處理文件,所有 worker 跑完再寫入?yún)R總報(bào)告
  • 啟動(dòng)多個(gè)微服務(wù)健康檢查,全通過(guò)才算就緒

WaitGroup 就是為這類"等待一組并發(fā)任務(wù)完成"的訴求設(shè)計(jì)的。

二、三個(gè)核心方法

WaitGroup 的 API 極為簡(jiǎn)潔——只有 Add、Done、Wait 三個(gè)方法:

var wg sync.WaitGroup

// Add(n): 計(jì)數(shù)器加 n,表示"我又派了 n 個(gè)任務(wù)"
wg.Add(3)

for i := 0; i < 3; i++ {
    go func(id int) {
        defer wg.Done() // 等價(jià)于 Add(-1),表示"我干完了"
        // ... 干活 ...
    }(i)
}

wg.Wait() // 阻塞直到計(jì)數(shù)器歸零

Done 內(nèi)部就是 wg.Add(-1),二者語(yǔ)義完全等價(jià)。

三、設(shè)計(jì)哲學(xué):誰(shuí)派生,誰(shuí) Add

這是最容易踩坑的地方。Add 必須在啟動(dòng) goroutine 之前調(diào)用,否則可能 Wait 先執(zhí)行,直接放行:

// ? 錯(cuò)誤:wg.Add 在 go 里面
for i := 0; i < 5; i++ {
    go func() {
        wg.Add(1)  // 嚴(yán)重錯(cuò)誤!Wait 可能在這之前就返回了
        defer wg.Done()
        doWork()
    }()
}
wg.Wait()

正確的做法是 Add 和 go 語(yǔ)句緊挨在一起:

// ? 正確
for i := 0; i < 5; i++ {
    wg.Add(1)
    go func() {
        defer wg.Done()
        doWork()
    }()
}
wg.Wait()

四、WaitGroup 是值類型,必須傳指針

WaitGroup 結(jié)構(gòu)體內(nèi)部維護(hù)了一個(gè)計(jì)數(shù)器,不能用做值拷貝的參數(shù)傳遞——副本中的加減不會(huì)影響原值。

// ? 值傳遞:計(jì)數(shù)器的修改對(duì)原始 wg 不可見(jiàn)
func run(wg sync.WaitGroup) {
    defer wg.Done()
}

// ? 指針傳遞
func run(wg *sync.WaitGroup) {
    defer wg.Done()
}

Go 1.20+ 編譯器會(huì)給出 copylock 警告,但不如一開(kāi)始就用對(duì)。

五、WaitGroup 不能重用"飛行中"的實(shí)例

WaitGroup 的內(nèi)部狀態(tài)有三個(gè)階段:0 → Add → Wait。Wait 返回后,WaitGroup 自動(dòng)回到 0 狀態(tài)可以重用。但如果在 Wait 還沒(méi)返回時(shí)又調(diào)用 Add,這屬于并發(fā)訪問(wèn)內(nèi)部狀態(tài),是數(shù)據(jù)競(jìng)爭(zhēng)。

// ? 數(shù)據(jù)競(jìng)爭(zhēng)!
go func() {
    wg.Add(1) // 與下面的 wg.Wait 并發(fā)
    defer wg.Done()
    doWork()
}()
wg.Wait() // 此時(shí)可能還沒(méi)有 Add 完成

六、練習(xí)代碼

保存為 waitgroup_demo.go

package main

import (
    "fmt"
    "math/rand"
    "sync"
    "time"
)

// 模擬從多個(gè)數(shù)據(jù)源獲取數(shù)據(jù)
type DataFetcher struct {
    name    string
    latency time.Duration // 模擬耗時(shí)
}

func (f DataFetcher) Fetch() string {
    time.Sleep(f.latency)
    return fmt.Sprintf("[%s 數(shù)據(jù)]", f.name)
}

func main() {
    rand.Seed(time.Now().UnixNano())

    // 三個(gè)數(shù)據(jù)源,各自耗時(shí)不同
    sources := []DataFetcher{
        {name: "數(shù)據(jù)庫(kù)", latency: 200 * time.Millisecond},
        {name: "Redis", latency: 50 * time.Millisecond},
        {name: "遠(yuǎn)程API", latency: 300 * time.Millisecond},
    }

    // 結(jié)果收集
    results := make([]string, len(sources))
    var wg sync.WaitGroup

    for i, src := range sources {
        wg.Add(1) // 在 go 之前 Add
        go func(idx int, fetcher DataFetcher) {
            defer wg.Done()
            results[idx] = fetcher.Fetch()
        }(i, src) // 注意:把循環(huán)變量傳給閉包
    }

    fmt.Println("等待所有數(shù)據(jù)源返回...")
    wg.Wait()
    fmt.Println("全部就緒!\n")

    for i, r := range results {
        fmt.Printf("數(shù)據(jù)源 %d (%s): %s\n", i, sources[i].name, r)
    }

    // —— 進(jìn)階:帶超時(shí)的 WaitGroup ——
    fmt.Println("\n——— 超時(shí)模式演示 ———")

    var wg2 sync.WaitGroup
    wg2.Add(1)
    go func() {
        defer wg2.Done()
        time.Sleep(2 * time.Second) // 模擬很慢的任務(wù)
        fmt.Println("慢任務(wù)完成")
    }()

    // 用一個(gè) channel 來(lái)通知"WaitGroup 已經(jīng)等完了"
    done := make(chan struct{})
    go func() {
        wg2.Wait()
        close(done)
    }()

    select {
    case <-done:
        fmt.Println("所有任務(wù)在超時(shí)前完成")
    case <-time.After(500 * time.Millisecond):
        fmt.Println("超時(shí)!不等了")
    }
}

運(yùn)行結(jié)果:

等待所有數(shù)據(jù)源返回...
全部就緒!

數(shù)據(jù)源 0 (數(shù)據(jù)庫(kù)): [數(shù)據(jù)庫(kù) 數(shù)據(jù)]
數(shù)據(jù)源 1 (Redis): [Redis 數(shù)據(jù)]
數(shù)據(jù)源 2 (遠(yuǎn)程API): [遠(yuǎn)程API 數(shù)據(jù)]

——— 超時(shí)模式演示 ———
超時(shí)!不等了

七、WaitGroup vs Channel

場(chǎng)景WaitGroupChannel
等待所有 goroutine 結(jié)束? 天然適合需要逐個(gè)接收,寫起來(lái)繁瑣
需要收集返回值需額外變量/鎖可以直接從 channel 讀取結(jié)果
限制并發(fā)數(shù)不適合用有緩沖 channel 做信號(hào)量
取消/超時(shí)需配合 select(見(jiàn)上文)配合 select + ctx.Done()

八、關(guān)鍵要點(diǎn)

要點(diǎn)說(shuō)明
Add 在前必須在 go 語(yǔ)句之前調(diào)用 Add
defer Done確保 goroutine panic 也能 Done
傳指針WaitGroup 是值類型,傳遞需取地址
Wait 可重入多個(gè) goroutine 可以同時(shí) Wait
超時(shí)模式Wait 本身不超時(shí),配合 channel + select 實(shí)現(xiàn)
不能復(fù)制復(fù)制 WaitGroup 的值會(huì)破壞內(nèi)部狀態(tài)

到此這篇關(guān)于Golang中sync.WaitGroup 等待組的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Golang sync.WaitGroup 等待組內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 深入解析Go語(yǔ)言中for循環(huán)的寫法

    深入解析Go語(yǔ)言中for循環(huán)的寫法

    這篇文章主要介紹了Go語(yǔ)言中for循環(huán)的寫法,是Golang入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-10-10
  • Golang迭代如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解

    Golang迭代如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解

    這篇文章主要為大家介紹了Golang迭代之如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • Go語(yǔ)言常見(jiàn)錯(cuò)誤之將接口定義在實(shí)現(xiàn)方

    Go語(yǔ)言常見(jiàn)錯(cuò)誤之將接口定義在實(shí)現(xiàn)方

    在Go中,接口起到一個(gè)十分關(guān)鍵的角色,它們提供了一種方式來(lái)定義對(duì)象的行為,而不需要知道對(duì)象的具體實(shí)現(xiàn),一個(gè)常見(jiàn)的錯(cuò)誤是在實(shí)現(xiàn)方而不是使用方定義接口,本文將詳細(xì)探討為何這樣做是一個(gè)錯(cuò)誤,以及如何避免它
    2024-01-01
  • GO語(yǔ)言中err接口及defer延遲異常處理分析

    GO語(yǔ)言中err接口及defer延遲異常處理分析

    這篇文章主要為大家介紹了GO語(yǔ)言中err接口及defer延遲異常處理的示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • 詳解如何使用Go的Viper來(lái)解析配置信息

    詳解如何使用Go的Viper來(lái)解析配置信息

    Viper庫(kù)為Golang語(yǔ)言開(kāi)發(fā)者提供了對(duì)不同數(shù)據(jù)源和不同格式的配置文件的讀取,是Go項(xiàng)目讀取配置的神器,我們今天就來(lái)講講如何使用Viper來(lái)解析配置信息,文中通過(guò)代碼示例講解非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • Go語(yǔ)言基礎(chǔ)if條件語(yǔ)句用法及示例詳解

    Go語(yǔ)言基礎(chǔ)if條件語(yǔ)句用法及示例詳解

    這篇文章主要為大家介紹了Go語(yǔ)言基礎(chǔ)if條件語(yǔ)句的用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2021-11-11
  • Golang使用Gin創(chuàng)建Restful API的實(shí)現(xiàn)

    Golang使用Gin創(chuàng)建Restful API的實(shí)現(xiàn)

    本文主要介紹了Golang使用Gin創(chuàng)建Restful API的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • Golang因Channel未關(guān)閉導(dǎo)致內(nèi)存泄漏的解決方案詳解

    Golang因Channel未關(guān)閉導(dǎo)致內(nèi)存泄漏的解決方案詳解

    這篇文章主要為大家詳細(xì)介紹了當(dāng)Golang因Channel未關(guān)閉導(dǎo)致內(nèi)存泄漏時(shí)蓋如何解決,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-07-07
  • go語(yǔ)言編程二維碼生成及識(shí)別

    go語(yǔ)言編程二維碼生成及識(shí)別

    這篇文章主要為大家介紹了go語(yǔ)言編程二維碼的生成及識(shí)別示例演示,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-04-04
  • Go語(yǔ)言使用swagger生成接口文檔的方法

    Go語(yǔ)言使用swagger生成接口文檔的方法

    這篇文章主要介紹了Go語(yǔ)言使用swagger生成接口文檔的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09

最新評(píng)論

梧州市| 嘉祥县| 昌平区| 庆城县| 寿宁县| 斗六市| 金坛市| 华安县| 修文县| 理塘县| 渝北区| 六枝特区| 灯塔市| 卓资县| 应用必备| 平乡县| 玉屏| 永年县| 商南县| 平凉市| 尚义县| 县级市| 抚松县| 深泽县| 邛崃市| 通山县| 通化市| 米泉市| 榆社县| 遵义县| 泗阳县| 牟定县| 青铜峡市| 松潘县| 阳信县| 和静县| 利川市| 民丰县| 乌拉特前旗| 泸溪县| 霍邱县|