内容目录
概述
在Go语言的并发模型中,sync.WaitGroup
是一种常见且重要的结构,它用于等待一组并发的goroutine结束。它的工作方式是:首先在主goroutine中调用Add
方法来设置需要等待的goroutine长度,当每个goroutine结束时,就调用Done
方法,最后在主goroutine中通过调用Wait
方法实现阻塞,直到所有的goroutine都结束,Wait
才会返回。
为了更好地理解并掌握WaitGroup
的功能,我们编写了一些测试用例,详细讲解了它的关键使用方法和注意事项。
基本用法
- 测试了一个常规的
WaitGroup
使用场景,在这个场景中,我们添加了一个需要等待的goroutine,并且等这个goroutine完成时,WaitGroup
的Wait
方法应该返回,这是最基础的使用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
- 用于测试多个并发的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的复用
- 用于测试
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等待同一个任务的完成
- 这个测试讲述了多个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的异常情况处理
- 这个测试讲述了一种特殊情况,即在我们添加了更多的需要等待的goroutine而比实际完成的goroutine数量时,
WaitGroup
的Wait
方法应当会阻塞,直至所有的等待的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)
}
- 这个测试情况中,如果我们试图添加负数的goroutine,那么
WaitGroup
会产生panic。
func TestWaitGroup_ShouldPanic_WhenAddNegtiveCounterWithoutAddPositiveCounter(t *testing.T) {
wg := sync.WaitGroup{}
assert.Panics(t, func() { wg.Add(-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)
}
- 这个测试讲述了在没有通过
Add
方法添加任何需要等待的Goroutine的情况下,直接调用Done
方法是否会抛出panic。
func TestWaitGroup_ShouldPanic_WhenNoAddCalled(t *testing.T) {
wg := sync.WaitGroup{}
assert.Panics(t, func() {
wg.Done()
})
}
在实际使用WaitGroup
时,一定要注意这些注意事项,以防止程序出现错误或异常。