面试题答案
一键面试WaitGroup计数器的增减操作实现
- 增加计数器:
- 使用
Add
方法来增加WaitGroup
的计数器。例如:
var wg sync.WaitGroup wg.Add(1)
Add
方法接受一个整数参数,该参数表示要增加的计数值。在实际使用中,通常在启动一个新的goroutine之前调用Add
,以告知WaitGroup
有新的任务需要等待。Add
方法会将传入的值加到内部的计数器上。如果在计数器已经归零后调用Add
,会导致运行时恐慌(panic
),除非是在Wait
方法被调用之前调用Add
,因为Wait
方法会等待计数器归零后才返回。
- 使用
- 减少计数器:
- 使用
Done
方法来减少WaitGroup
的计数器。Done
方法实际上是Add(-1)
的快捷方式。例如:
var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() // 执行任务 }()
- 通常在goroutine执行完成任务后调用
Done
,表示该goroutine的工作已经完成,WaitGroup
的计数器应减1。这样,当所有相关的goroutine都调用了Done
后,计数器归零,等待的Wait
操作将被解除阻塞。 - 也可以直接调用
Add(-1)
来减少计数器,但使用Done
更清晰和安全,因为Done
不容易出错,特别是在并发环境下。
- 使用
实际应用中的陷阱及避免方法
- 重复调用
Add
导致恐慌:- 陷阱:如果在计数器已经归零后,再次调用
Add
方法,并且Wait
方法没有在调用Add
之前调用,会导致运行时恐慌。例如:
var wg sync.WaitGroup wg.Add(1) wg.Done() wg.Add(1) // 这会导致恐慌
- 避免方法:确保只在
Wait
方法被调用之前调用Add
,并且调用Add
的次数与预期的要等待的goroutine数量精确匹配。在启动goroutine之前一次性调用Add
,避免在运行过程中重复调用Add
。
- 陷阱:如果在计数器已经归零后,再次调用
- 忘记调用
Done
:- 陷阱:如果某个goroutine没有调用
Done
方法,WaitGroup
的计数器将不会归零,Wait
方法会一直阻塞,导致程序无法继续执行。例如:
var wg sync.WaitGroup wg.Add(2) go func() { // 忘记调用wg.Done() }() go func() { defer wg.Done() }() wg.Wait() // 这里会一直阻塞,因为第一个goroutine没有调用Done
- 避免方法:在每个启动的goroutine中,无论是正常结束还是发生错误,都要确保调用
Done
方法。使用defer
语句是一种很好的方式,因为它可以保证即使在goroutine中发生恐慌,Done
也会被调用。
- 陷阱:如果某个goroutine没有调用
- 在错误场景下未正确处理
WaitGroup
:- 陷阱:如果在goroutine中发生错误,没有适当处理
WaitGroup
的计数器,可能导致计数器状态不正确,Wait
方法无法正确工作。例如:
var wg sync.WaitGroup wg.Add(1) go func() { if err := someFunction(); err!= nil { // 这里没有调用wg.Done() return } wg.Done() }() wg.Wait() // 如果someFunction返回错误,计数器不会归零,这里会一直阻塞
- 避免方法:在发生错误时,也要调用
Done
方法,以确保WaitGroup
的计数器正确减少。可以在错误处理逻辑中添加defer wg.Done()
,或者在函数开头就使用defer
来保证无论是否发生错误,Done
都会被调用。例如:
var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() if err := someFunction(); err!= nil { // 处理错误逻辑 return } }() wg.Wait()
- 陷阱:如果在goroutine中发生错误,没有适当处理