逐步学习Go-WaitGroup

内容纲要

概述

在Go语言的并发模型中,sync.WaitGroup是一种常见且重要的结构,它用于等待一组并发的goroutine结束。它的工作方式是:首先在主goroutine中调用Add方法来设置需要等待的goroutine长度,当每个goroutine结束时,就调用Done方法,最后在主goroutine中通过调用Wait方法实现阻塞,直到所有的goroutine都结束,Wait才会返回。

为了更好地理解并掌握WaitGroup的功能,我们编写了一些测试用例,详细讲解了它的关键使用方法和注意事项。

基本用法

  1. 测试了一个常规的WaitGroup使用场景,在这个场景中,我们添加了一个需要等待的goroutine,并且等这个goroutine完成时,WaitGroupWait方法应该返回,这是最基础的使用WaitGroup的场景。
func TestWaitGroup_ShouldComplete_WhenAddCountEqualsDoneCount(t *testing.T) {
    wg := sync.WaitGroup{}

    wg.Add(1)
    go func() {
        println("Hello WaitGroup")
        wg.Done()
    }()

    wg.Wait()

    completed := 1
    assert.Equal(t, 1, completed)
}

处理多个Goroutine

  1. 用于测试多个并发的Goroutine是否能正确地被WaitGroup跟踪并同步。我们在这个场景中创建了多个并发的Goroutine,同时每个Goroutine在完成其任务之后都会调用Done方法进行标记,然后我们在主Goroutine中通过Wait函数阻塞并等待所有的Goroutine完成。
func TestWaitGroup_ShouldCompleted_WhenMultipleGoroutineDoNormalAddAndDone(t *testing.T) {
    wg := sync.WaitGroup{}

    loopCount := 100

    wg.Add(loopCount)

    for i := 0; i < loopCount; i++ {
        go func(i int) {
            println("executed ", i, "")
            wg.Done()
        }(i)
    }

    isSuccess := true

    wg.Wait()

    assert.True(t, isSuccess)
}

WaitGroup的复用

  1. 用于测试WaitGroup对象在其Wait方法返回后是否可以再次复用来等待新的并发Goroutine。
func TestWaitGroup_ShouldCompleted_WhenReuseWaitGroupAfterWaitReturnedAtFirstRound(t *testing.T) {
    wg := sync.WaitGroup{}

    loopCount := 100

    // 第一轮
    wg.Add(loopCount)

    for i := 0; i < loopCount; i++ {
        go func(i int) {
            println("executed ", i, "")
            wg.Done()
        }(i)
    }

    isFirstRoundSuccess := false
    wg.Wait()
    isFirstRoundSuccess = true

    // 第二轮
    wg.Add(loopCount)

    for i := 0; i < loopCount; i++ {
        go func(i int) {
            println("executed ", i, "")
            wg.Done()
        }(i)
    }

    isNextRoundSuccess := false

    wg.Wait()

    isNextRoundSuccess = true

    assert.True(t, isFirstRoundSuccess)
    assert.True(t, isNextRoundSuccess)
}

多个Goroutine等待同一个任务的完成

  1. 这个测试讲述了多个Goroutine在同时等待一个WaitGroup的场景,在这个测试中,我们添加了一个Goroutine到WaitGroup,而这个Goroutine会在一段时间之后调用Done方法,而在其它的Goroutine中,它们则是通过调用Wait方法来等待这个WaitGroup,所有等待这个WaitGroup的Goroutine都应当在Done方法被调用之后解锁。
func TestWaitGroup_ShouldAllReturned_WhenMultipleGoroutineWaitForDone(t *testing.T) {
    wg := sync.WaitGroup{}

    wg.Add(1)
    go func() {
        time.Sleep(time.Second)
        wg.Done()
        println("Done")

    }()

    go func() {
        wg.Wait()
        println("Wait complete 1")
    }()

    go func() {
        wg.Wait()
        println("Wait complete 2")
    }()
    isSuccess := false
    wg.Wait()
    isSuccess = true

    assert.True(t, isSuccess)
}

WaitGroup的异常情况处理

  1. 这个测试讲述了一种特殊情况,即在我们添加了更多的需要等待的goroutine而比实际完成的goroutine数量时,WaitGroupWait方法应当会阻塞,直至所有的等待的goroutine完成。
func TestWaitGroup_ShouldBlocked_WhenAddCountGreaterThanDoneCount(t *testing.T) {
    wg := sync.WaitGroup{}
    ch := make(chan bool)

    wg.Add(2)
    go func() {
        println("Hello WaitGroup")
        wg.Done()
    }()

    go func() {
        wg.Wait()
        ch <- true
    }()

    isCompleted := false
    isTimeout := false
    select {
    case <-ch:
        isCompleted = true
    case <-time.After(10 * time.Second):
        isTimeout = true
    }

    assert.False(t, isCompleted)
    assert.True(t, isTimeout)
}
  1. 这个测试情况中,如果我们试图添加负数的goroutine,那么WaitGroup会产生panic。
func TestWaitGroup_ShouldPanic_WhenAddNegtiveCounterWithoutAddPositiveCounter(t *testing.T) {
    wg := sync.WaitGroup{}

    assert.Panics(t, func() { wg.Add(-1) })
}
  1. 这个测试讲述了如果我们在一个Wait调用还未返回的时候,试图复用WaitGroup,那么WaitGroup会产生panic。
func TestWaitGroup_ShouldPainic_WhenTryToReusePreviousWaitHasReturned(t *testing.T) {
    var wg sync.WaitGroup

    runtime.GOMAXPROCS(3)

    wg.Add(1)

    go func() {
        fmt.Println("Wait executed")
        wg.Wait()
        fmt.Println("Wait completed")
    }()

    go func() {
        time.Sleep(time.Second * 5)
        wg.Done()
        fmt.Println("Add executed")
        wg.Add(1)
    }()

    go func() {
        time.Sleep(time.Second * 5)
        fmt.Println("Done executed")
        wg.Done()
    }()

    time.Sleep(100 * time.Second)
}
  1. 这个测试讲述了在没有通过Add方法添加任何需要等待的Goroutine的情况下,直接调用Done方法是否会抛出panic。
func TestWaitGroup_ShouldPanic_WhenNoAddCalled(t *testing.T) {
    wg := sync.WaitGroup{}

    assert.Panics(t, func() {
        wg.Done()
    })
}

在实际使用WaitGroup时,一定要注意这些注意事项,以防止程序出现错误或异常。

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部