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

Go語言學習之WaitGroup用法詳解

 更新時間:2022年06月11日 08:23:30   作者:OOMO1Edc  
Go語言中的?WaitGroup?和?Java?中的?CyclicBarrier、CountDownLatch?非常類似。本文將詳細為大家講講WaitGroup的用法,感興趣的小伙伴可以跟隨小編一起學習一下

前言

在前面的文章中,我們使用過 WaitGroup 進行任務編排,Go語言中的 WaitGroup 和 Java 中的 CyclicBarrier、CountDownLatch 非常類似。比如我們有一個主任務在執(zhí)行,執(zhí)行到某一點時需要并行執(zhí)行三個子任務,并且需要等到三個子任務都執(zhí)行完后,再繼續(xù)執(zhí)行主任務。那我們就需要設置一個檢查點,使主任務一直阻塞在這,等三個子任務執(zhí)行完后再放行。

說明:本文中的示例,均是基于Go1.17 64位機器

小試牛刀

我們先來個簡單的例子,看下 WaitGroup 是怎么使用的。示例中使用 Add(5) 表示我們有 5個 子任務,然后起了 5個 協程去完成任務,主協程使用 Wait() 方法等待 子協程執(zhí)行完畢,輸出一共等待的時間。

func main() {
    var waitGroup sync.WaitGroup

    start := time.Now()
    waitGroup.Add(5)
    for i := 0; i < 5; i++ {
        go func() {
            defer waitGroup.Done()
            time.Sleep(time.Second)
            fmt.Println("done")
        }()
    }

    waitGroup.Wait()
    fmt.Println(time.Now().Sub(start).Seconds())
}

/*
done
done
done
done
done
1.000306089
*/

總覽

WaitGroup 一共有三個方法:

(wg *WaitGroup) Add(delta int)
(wg *WaitGroup) Done()
(wg *WaitGroup) Wait()
  • Add 方法用于設置 WaitGroup 的計數值,可以理解為子任務的數量
  • Done 方法用于將 WaitGroup 的計數值減一,可以理解為完成一個子任務
  • Wait 方法用于阻塞調用者,直到 WaitGroup 的計數值為0,即所有子任務都完成

正常來說,我們使用的時候,需要先確定子任務的數量,然后調用 Add() 方法傳入相應的數量,在每個子任務的協程中,調用 Done(),需要等待的協程調用 Wait() 方法,狀態(tài)流轉如下圖:

底層實現

結構體

type WaitGroup struct {
    noCopy noCopy // noCopy 字段標識,由于 WaitGroup 不能復制,方便工具檢測

    state1 [3]uint32  // 12個字節(jié),8個字節(jié)標識 計數值和等待數量,4個字節(jié)用于標識信號量
}

state1 是個復合字段,會拆分為兩部分: 64位(8個字節(jié))的 statep 作為一個整體用于原子操作, 其中前面4個字節(jié)表示計數值,后面四個字節(jié)表示等待數量;剩余 32位(4個字節(jié))semap 用于標識信號量。

Go語言中對于64位的變量進行原子操作,需要保證該變量是64位對齊的,也就是要保證這 8個字節(jié) 的首地址是 8 的整數倍。因此當 state1 的首地址是 8 的整數倍時,取前8個字節(jié)作為 statep ,后4個字節(jié)作為 semap;當 state1 的首地址不是 8 的整數倍時,取后8個字節(jié)作為 statep ,前4個字節(jié)作為 semap

func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {
    
    // 首地址是8的倍數時,前8個字節(jié)為 statep, 后四個字節(jié)為 semap
    if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
        return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]
    } else { 
        
    // 后8個字節(jié)為 statep, 前四個字節(jié)為 semap    
        return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]
    }
}

Add

  • Add 方法用于添加一個計數值(負數相當于減),當計數值變?yōu)?后, Wait 方法阻塞的所有等待者都會被釋放
  • 計數值變?yōu)樨摂凳欠欠ú僮鳎a生 panic
  • 當計數值為0時(初始狀態(tài)),Add 方法不能和 Wait 方法并發(fā)調用,需要保證 Add 方法在 Wait 方法之前調用,否則會 panic
func (wg *WaitGroup) Add(delta int) {

    // 拿到計數值等待者變量 statep 和 信號量 semap
    statep, semap := wg.state()

    // 計數值加上 delta: statep 的前四個字節(jié)是計數值,因此將 delta 前移 32位
    state := atomic.AddUint64(statep, uint64(delta)<<32)

    // 計數值
    v := int32(state >> 32)

    // 等待者數量
    w := uint32(state)

    // 如果加上 delta 之后,計數值變?yōu)樨摂?,不合法,panic
    if v < 0 {
        panic("sync: negative WaitGroup counter")
    }

    // delta > 0 && v == int32(delta) : 表示從 0 開始添加計數值
    // w!=0 :表示已經有了等待者
    // 說明在添加計數值的時候,同時添加了等待者,非法操作。添加等待者需要在添加計數值之后
    if w != 0 && delta > 0 && v == int32(delta) {
        panic("sync: WaitGroup misuse: Add called concurrently with Wait")
    }

    // v>0 : 計數值不等于0,不需要喚醒等待者,直接返回
    // w==0: 沒有等待者,不需要喚醒,直接返回
    if v > 0 || w == 0 {
        return
    }

    // 再次檢查數據是否一致
    if *statep != state {
        panic("sync: WaitGroup misuse: Add called concurrently with Wait")
    }

    // 到這里說明計數值為0,且等待者大于0,需要喚醒所有的等待者,并把系統(tǒng)置為初始狀態(tài)(0狀態(tài))
  
  // 將計數值和等待者數量都置為0
    *statep = 0

    // 喚醒等待者
    for ; w != 0; w-- {
        runtime_Semrelease(semap, false, 0)
    }
}

Done

// 完成一個任務,將計數值減一,當計數值減為0時,需要喚醒所有的等待者
func (wg *WaitGroup) Done() {
    wg.Add(-1)
}

Wait

// 調用 Wait 方法會被阻塞,直到 計數值 變?yōu)?
func (wg *WaitGroup) Wait() {

    // 獲取計數、等待數和信號量
    statep, semap := wg.state()

    for {
        state := atomic.LoadUint64(statep)

        // 計數值
        v := int32(state >> 32)

        // 等待者數量
        w := uint32(state)

        // 計數值數量為0,直接返回,無需等待
        if v == 0 {
            return
        }

        // 到這里說明計數值數量大于0
        // 增加等待者數量:這里會有競爭,比如多個 Wait 調用,或者在同時調用 Add 方法,增加不成功會繼續(xù) for 循環(huán)
        if atomic.CompareAndSwapUint64(statep, state, state+1) {
            // 增加成功后,阻塞在信號量這里,等待被喚醒
            runtime_Semacquire(semap)

            // 被喚醒的時候,應該是0狀態(tài)。如果重用 WaitGroup,需要等 Wait 返回
            if *statep != 0 {
                panic("sync: WaitGroup is reused before previous Wait has returned")
            }
            return
        }
    }
}

易錯點

上面分析源碼可以看到幾個會產生 panic 的點,這也是我們使用 WaitGroup 需要注意的地方

1.計數值變?yōu)樨摂?/p>

調用 Add 時參數值傳負數

func main() {
var wg sync.WaitGroup
wg.Add(1)
wg.Add(-1)
wg.Add(-1)
}

多次調用 Done 方法

func main() {
    var wg sync.WaitGroup

    wg.Add(1)
    go func() {
        fmt.Println("test")
        wg.Done()
        wg.Done()
    }()

    time.Sleep(time.Second)
    wg.Wait()
}

2.Add 和 Wait 并發(fā)調用

Add 和 Wait 并發(fā)調用,有可能達不到我們預期的效果,甚至 panic。如下示例中,我們想要等待 3 個子任務都執(zhí)行完后再執(zhí)行主任務,但實際情況可能是子任務還沒起來,主任務就繼續(xù)往下執(zhí)行了。

func doSomething(wg *sync.WaitGroup) {
    wg.Add(1)
    fmt.Println("do  something")
    defer wg.Done()
}

func main() {
    var wg sync.WaitGroup
    for i := 0; i < 3; i++ {
        go doSomething(&wg)
    }

    wg.Wait()
    fmt.Println("main")
}

//main
//do  something
//do  something

正確的使用方式,應該是在調用 Wait 前先調用 Add

func doSomething(wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("do  something")
}

func main() {
    var wg sync.WaitGroup
    wg.Add(3)
    for i := 0; i < 3; i++ {
        go doSomething(&wg)
    }

    wg.Wait()
    fmt.Println("main")
}

//do  something
//do  something
//do  something
//main

3.沒有等 Wait 返回,就重用 WaitGroup

func main() {
    var wg sync.WaitGroup

    wg.Add(1)
    go func() {
        fmt.Println("do something")
        wg.Done()
        wg.Add(1)
    }()

    wg.Wait()
}

4.復制使用

我們知道 Go 語言中的參數傳遞,都是值傳遞,就會產生復制操作。因此在向函數傳遞 WaitGroup 時,使用指針進行操作。

// 錯誤使用方式,沒有使用指針
func doSomething(wg sync.WaitGroup) {
    fmt.Println("do  something")
    defer wg.Done()
}

func main() {
    var wg sync.WaitGroup
    wg.Add(3)
    for i := 0; i < 3; i++ {
        // 這里沒使用指針,wg狀態(tài)一直不會改變,導致 Wait 一直阻塞
        go doSomething(wg)
    }

    wg.Wait()
    fmt.Println("main")
}

總結

我們通過源碼+示例的方式,一起學習了 sync.WaitGroup 實現邏輯,同時也給出了一些注意點,只要做到如下操作,就不會出現問題:

  • 保證 Add 在 Wait 前調用
  • Add 中不傳遞負數
  • 任務完成后不要忘記調用 Done 方法,建議使用 defer wg.Done()
  • 不要復制使用 WaitGroup,函數傳遞時使用指針傳遞
  • 盡量不復用 WaigGroup,減少出問題的風險

到此這篇關于Go語言學習之WaitGroup用法詳解的文章就介紹到這了,更多相關Go語言 WaitGroup內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Go語言依賴管理三要素示例解析

    Go語言依賴管理三要素示例解析

    這篇文章主要介紹了Go語言依賴管理三要素及示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01
  • go 語言字符類型 byte 與 rune案例詳解

    go 語言字符類型 byte 與 rune案例詳解

    這篇文章主要介紹了go 語言字符類型 byte 與 rune案例詳解,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-03-03
  • 重學Go語言之錯誤處理與異常機制詳解

    重學Go語言之錯誤處理與異常機制詳解

    Go語言的開發(fā)者顯然覺得?try-catch被濫用了,因此?Go不支持使用?try-catch語句捕獲異常處理,那么,Go語言是如何定義和處理程序的異常呢,下面我們就來看看吧
    2023-08-08
  • 深入解析快速排序算法的原理及其Go語言版實現

    深入解析快速排序算法的原理及其Go語言版實現

    這篇文章主要介紹了快速排序算法的原理及其Go語言版實現,文中對于快速算法的過程和效率有較為詳細的說明,需要的朋友可以參考下
    2016-04-04
  • 使用go語言實現cors中間件

    使用go語言實現cors中間件

    CORS是一種瀏覽器安全機制,用于控制在Web應用程序中不同源(Origin)之間的資源共享,本文將給大家介紹如何使用go語言實現cors中間件,文中有詳細的代碼示例供大家參考,需要的朋友可以參考下
    2023-09-09
  • Go實現完全靜態(tài)編譯和交叉編譯的示例代碼

    Go實現完全靜態(tài)編譯和交叉編譯的示例代碼

    Go 語言天生支持跨平臺編譯,并且其標準庫幾乎不依賴系統(tǒng)動態(tài)庫,所以在大多數場景下,它編譯出來的二進制文件幾乎可以直接丟到任何機器運行,但實際開發(fā)中,我們經常遇到兩個問題,如何完全靜態(tài)編譯和交叉編譯,本文詳細的給大家介紹了解決方法,需要的朋友可以參考下
    2025-07-07
  • 用golang如何替換某個文件中的字符串

    用golang如何替換某個文件中的字符串

    這篇文章主要介紹了用golang實現替換某個文件中的字符串操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Go語言基礎Json序列化反序列化及文件讀寫示例詳解

    Go語言基礎Json序列化反序列化及文件讀寫示例詳解

    這篇文章主要為大家介紹了Go語言基礎Json序列化反序列化以及文件讀寫的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-11-11
  • Go爬蟲(http、goquery和colly)詳解

    Go爬蟲(http、goquery和colly)詳解

    goquery可以避免操作復雜的正則表達式,它可以直接根據url獲取一個Document對象,然后根據標簽選擇器、類選擇器和id選擇器獲取相應的選擇對象,進行自定義的操作,這篇文章主要介紹了Go爬蟲(http、goquery和colly),需要的朋友可以參考下
    2022-09-09
  • Go 泛型中的 [0]func(T)的實現

    Go 泛型中的 [0]func(T)的實現

    本文主要介紹了Go 泛型中的 [0]func(T)的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2026-02-02

最新評論

云阳县| 兖州市| 罗源县| 西乡县| 金坛市| 阳东县| 三台县| 五常市| 南涧| 玛沁县| 昌黎县| 招远市| 平江县| 永和县| 阿克苏市| 留坝县| 长汀县| 望谟县| 高唐县| 渭南市| 汾阳市| 志丹县| 四子王旗| 长阳| 肇东市| 什邡市| 大理市| 呈贡县| 哈巴河县| 北安市| 定结县| 乌审旗| 上栗县| 孝感市| 泾阳县| 岑溪市| 浦北县| 崇礼县| 佛坪县| 龙南县| 昌吉市|