
在go语言的并发编程中,一个常见场景是启动多个goroutine(工作者),它们各自执行一部分任务,并将结果发送到同一个共享通道(ch)。主goroutine负责从这个通道接收并处理这些结果。此时,一个关键问题是如何判断所有工作者goroutine何时完成其任务,并确保所有结果都被正确处理。
最初的尝试可能包括为每个工作者Goroutine设置一个独立的“完成”通道(done),并在主Goroutine中计数,直到所有工作者都发送了完成信号。例如:
package main
import "fmt"
const N = 10
func main() {
ch := make(chan int, N)
done := make(chan bool) // 非惯用:用于计数Goroutine完成状态
for i := 0; i < N; i++ {
go (func(n int, ch chan int, done chan bool) {
for i := 0; i < N; i++ {
ch <- n*N + i
}
done <- true // 发送完成信号
})(i, ch, done)
}
numDone := 0
for numDone < N { // 等待所有Goroutine完成
select {
case i := <-ch:
fmt.Println(i)
case <-done:
numDone++
}
}
// 清理循环:确保在所有done信号收到后,ch中剩余的数据也被处理
for {
select {
case i := <-ch:
fmt.Println(i)
default:
return
}
}
}这种方法虽然可以工作,但存在几个缺点:
Go语言标准库中的sync.WaitGroup是专门为等待一组Goroutine完成而设计的同步原语。结合通道的close操作和for range循环,可以构建出更加简洁、高效且符合Go语言哲学的并发模式。
sync.WaitGroup有三个主要方法:
立即学习“go语言免费学习笔记(深入)”;
使用sync.WaitGroup的典型模式是在主Goroutine中调用Add来设置需要等待的Goroutine数量,然后在每个工作者Goroutine中,使用defer wg.Done()确保无论Goroutine如何退出(正常完成或发生panic),计数器都会被正确减少。最后,主Goroutine调用wg.Wait()来等待所有工作者完成。
在所有数据生产者(工作者Goroutine)都完成其发送任务后,应该关闭共享通道。关闭通道有以下重要意义:
注意事项:
结合sync.WaitGroup和通道关闭,上述问题的惯用解决方案如下:
package main
import (
"fmt"
"sync" // 引入sync包
)
const N = 10
func main() {
ch := make(chan int, N)
var wg sync.WaitGroup // 声明WaitGroup
for i := 0; i < N; i++ {
wg.Add(1) // 每启动一个Goroutine,计数器加1
go func(n int) {
defer wg.Done() // Goroutine完成时,计数器减1
for i := 0; i < N; i++ {
ch <- n*N + i
}
}(i)
}
// 启动一个独立的Goroutine来等待所有工作者完成并关闭通道
go func() {
wg.Wait() // 阻塞直到所有工作者Goroutine都调用了Done()
close(ch) // 所有数据发送完毕,关闭通道
}()
// 使用for range循环从通道接收数据,直到通道关闭且数据全部取完
for i := range ch {
fmt.Println(i)
}
}在这个改进后的代码中:
通过上述示例,我们可以得出以下Go语言并发编程的最佳实践:
遵循这些惯用模式,可以编写出更具可读性、健壮性和Go语言风格的并发代码。
以上就是Go语言中并发任务协作与完成状态检测的惯用模式的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号