Golang 并发 WaitGroup
WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。
WaitGroup 是一个计数信号量,可以用来记录并维护运行的 goroutine。如果 WaitGroup
的值大于 0,Wait 方法就会阻塞。
WaitGroup 使用
func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
示例如下:
func TestWaitGroup(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("1 goroutine sleep ...")
time.Sleep(2 * time.Second)
fmt.Println("1 goroutine exit ...")
}()
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("2 goroutine sleep ...")
time.Sleep(4 * time.Second)
fmt.Println("2 goroutine exit ...")
}()
fmt.Println("waiting for all goroutine ")
wg.Wait()
fmt.Println("All goroutines finished!")
}
输出结果
waiting for all goroutine
2 goroutine sleep ...
1 goroutine sleep ...
1 goroutine exit ...
2 goroutine exit ...
All goroutines finished!
异常:
要注意Add和Done函数一定要配对,否则可能发生死锁,所报的错误信息如下:
fatal error: all goroutines are asleep - deadlock!
WaitGroup在需要等待多个任务结束再返回的业务来说还是很有用的。
但现实中用的更多的可能是,先等待一个协程组,若所有协程组都正确完成,则一直等到所有协程组结束;若其中有一个协程发生错误,则告诉协程组的其他协程,全部停止运行(本次任务失败)以免浪费系统资源。
该场景WaitGroup是无法实现的,那么该场景该如何实现呢,就需要用到通知机制,其实也可以用channel来实现。
WaitGroup 时如何实现超时机制
WaitGroup如何实现超时机制呢?任务不能一直等待下去,如果超时之后,希望程序可以继续进行,不影响主流程的调用。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
wg := sync.WaitGroup{}
wg.Add(1)
done := make(chan struct{})
go func() {
wg.Wait()
done <- struct{}{}
}()
go func() {
time.Sleep(5 * time.Second)
wg.Done()
}()
timeout := time.Duration(10) * time.Second
fmt.Printf("Wait for waitgroup (up to %s)\n", timeout)
select {
case <-done:
fmt.Printf("Wait group finished\n")
case <-time.After(timeout):
fmt.Printf("Timed out waiting for wait group\n")
}
fmt.Printf("Free at last\n")
}
WaitGroup 一组协程之间,其中一个协程发生错误,如何全部停止
等待一个协程组全部正确完成则结束;但其中一个协程发生错误,这时候就会阻塞,全部停止运行(本次任务失败)以免浪费系统资源,此时可以结合通道(channel)或者 select 语句两种方式来处理。
1、考虑使用两个通道:一个用于报告错误,另一个用于通知所有协程停止。在协程内部捕获错误,并将错误信息发送到错误通道。另一个协程监听错误通道,一旦有错误发生,就会向停止通知通道发送信号,通知所有协程停止运行。
func worker(id int, errCh chan error, wg *sync.WaitGroup) {
defer wg.Done()
// 模拟错误
if id == 2 {
errCh <- fmt.Errorf("error occurred in worker %d", id)
return
}
// 做一些工作
fmt.Printf("Worker %d is working...\n", id)
}
func TestWaitGroupError(t *testing.T) {
var wg sync.WaitGroup
errCh := make(chan error)
stopCh := make(chan struct{})
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(i, errCh, &wg)
}
go func() {
err := <-errCh
if err != nil {
fmt.Println("Error occurred:", err)
close(stopCh)
}
}()
go func() {
wg.Wait()
close(errCh)
}()
// 等待通知,停止所有协程
<-stopCh
fmt.Println("All workers stopped.")
}
errCh 通道用于发送错误信息,stopCh 通道用于通知所有协程停止。如果有一个协程发生错误,就会关闭 stopCh 通道,通知其他协程停止运行。
2、select 语句是 Go 中用于多路非阻塞的关键字,可以用于监听多个通道的操作。可以在 select 语句中组合使用 WaitGroup 和通道来实现对协程组的控制。