sync.WaitGroup在Golang中如何等待所有goroutine完成任务

sync.WaitGroup通过计数器协调goroutine完成。1. 初始化后,在启动goroutine前调用Add增加计数;2. 每个goroutine结束时调用Done减少计数,推荐使用defer wg.Done()确保执行;3. 主goroutine调用Wait阻塞,直到计数器归零。常见陷阱是Add调用时机错误,必须在go语句前执行,否则可能导致竞态条件或死锁。最佳实践包括:始终在启动goroutine前Add,使用defer wg.Done()保证通知,避免复制WaitGroup。

sync.waitgroup在golang中如何等待所有goroutine完成任务

sync.WaitGroup

在Golang中主要通过一个内部计数器来协调goroutine的完成。当需要等待一组goroutine时,我们会在启动它们之前调用

Add

方法增加计数,每个goroutine完成任务后调用

Done

方法递减计数。主goroutine则通过调用

Wait

方法阻塞,直到这个计数器归零,表示所有被追踪的goroutine都已完成。

当我们在Golang中处理并发任务时,经常会遇到一个场景:启动多个goroutine去执行一些工作,然后主goroutine需要等待所有这些子goroutine都完成,才能继续执行后续逻辑,或者安全地退出程序。

sync.WaitGroup

就是为此而生的。

它的核心机制其实很简单:

初始化:声明一个

var wg sync.WaitGroup

计数器增加:在启动任何需要等待的goroutine之前,调用

wg.Add(delta int)

。这个

delta

通常是你即将启动的goroutine数量。每调用一次

Add(1)

,内部计数器就加一。任务完成通知:在每个goroutine内部,当其任务即将完成时,调用

wg.Done()

。这会将内部计数器减一。一个非常好的实践是使用

defer wg.Done()

,这样即使goroutine内部发生panic,

Done()

也能被执行,避免主goroutine永远等待。等待:在主goroutine中,调用

wg.Wait()

。这个方法会阻塞当前goroutine,直到

WaitGroup

的内部计数器变为零。

来看个例子,这能让你更直观地理解:

立即学习“go语言免费学习笔记(深入)”;

package mainimport (    "fmt"    "sync"    "time")func worker(id int, wg *sync.WaitGroup) {    // 确保无论如何,这个goroutine完成时都会通知WaitGroup    defer wg.Done()     fmt.Printf("Worker %d starting...n", id)    time.Sleep(time.Duration(id) * time.Second) // 模拟工作    fmt.Printf("Worker %d finished.n", id)}func main() {    var wg sync.WaitGroup    numWorkers := 3    fmt.Println("Main: Launching workers...")    for i := 1; i <= numWorkers; i++ {        wg.Add(1) // 每启动一个worker,计数器加1        go worker(i, &wg)    }    fmt.Println("Main: Waiting for all workers to complete...")    wg.Wait() // 阻塞直到所有worker都调用了Done()    fmt.Println("Main: All workers completed. Exiting.")}

运行这段代码,你会看到主goroutine在启动所有

worker

之后,会一直等到所有

worker

都打印出”finished”才继续执行

"Main: All workers completed. Exiting."

。这正是我们想要的效果。

我个人在使用

WaitGroup

时,最常犯的一个小错误,或者说容易忽略的细节,就是

Add

的时机。它必须在对应的

go

语句之前调用。如果你在

go worker(i, &wg)

之后才

wg.Add(1)

,那么在某些极端情况下,如果

worker

执行得特别快,可能在

Add

被调用之前就已经

Done

了,这会导致计数器不匹配,

Wait

可能会提前返回或者永远等待。所以,顺序很重要。

Golang

sync.WaitGroup

有哪些常见的陷阱和最佳实践?

在使用

sync.WaitGroup

时,虽然它看起来直观,但

以上就是sync.WaitGroup在Golang中如何等待所有goroutine完成任务的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/1401612.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年12月15日 17:58:27
下一篇 2025年12月15日 17:58:41

相关推荐

  • Golang中如何使用context.WithCancel实现一个可中断的循环

    答案:context.WithCancel通过创建可取消的Context实现循环中断,调用cancel()函数通知所有监听goroutine退出,配合select监听ctx.Done()实现优雅终止。 在Golang中,要实现一个可中断的循环, context.WithCancel 是一个非常核心且…

    好文分享 2025年12月15日
    000
  • Golang结构体(struct)中如何定义匿名字段

    Go中的匿名字段通过嵌入类型实现类似继承的行为,如Employee嵌入Person可直接访问其字段;2. 访问时可直接使用emp.Name,无需通过emp.Person.Name;3. 若外部结构体有同名字段,则覆盖内部成员,需显式通过emp.Person.Name访问被覆盖字段;4. 匿名字段提升…

    2025年12月15日
    000
  • 理解Golang中的零值(zero value)概念及其重要性

    零值是Go语言中变量未初始化时的默认值,确保程序安全与确定性。数值类型为0,布尔类型为false,字符串为空字符串””,指针、切片、映射、通道、函数和接口为nil,结构体各字段取对应类型的零值。该机制简化了代码并提升安全性,如map查找、错误返回值和配置结构体中零值的合理使用…

    2025年12月15日
    000
  • Golang中的const和iota如何使用 详解常量生成器的妙用

    在 golang 中,const 用于声明不可变常量,iota 是常量生成器,从 0 开始自动递增。1. iota 在 const 块内使用,每新增一项自动加一,适合定义连续整型常量或枚举类型;2. 可通过手动赋值改变起始数,如 sunday = iota + 1 让枚举从 1 开始;3. 使用 _…

    2025年12月15日 好文分享
    000
  • Golang中select语句的default分支在非阻塞操作中的作用

    default分支用于避免select阻塞,使程序在无就绪case时执行默认操作,保持响应性,但需防止忙等待。 Golang中 select 语句的 default 分支主要作用是在没有其他 case 可以执行时,避免 select 语句阻塞。它允许程序在没有数据可接收或发送时,执行一些默认的操作,…

    2025年12月15日
    000
  • 讲解Golang环境变量中GOBIN的作用和配置方式

    GOBIN是go install命令安装可执行文件的目标目录,配置GOBIN并将其加入PATH后,可在任意位置运行Go工具。在Go Modules时代,GOBIN取代了早期$GOPATH/bin的角色,提供独立于项目路径的统一工具存放位置。若未将GOBIN添加到PATH,即使正确设置也无法直接调用工…

    2025年12月15日
    000
  • Golang中为什么recover必须在defer函数中直接调用才有效

    recover必须直接在defer函数中调用,因为只有在此时它才能捕获正在发生的panic。当panic触发堆栈解退,defer函数被执行,recover通过检查调用上下文判断是否处于panic状态,若被封装在间接函数中则无法感知panic,导致失效。Go语言此设计确保了恢复机制的明确性与可控性,避…

    2025年12月15日
    000
  • Golang中如何将一个大的package拆分成多个小的子package

    拆分Go包的核心是按职责边界将代码重构为高内聚、低耦合的子包,通过创建子目录、调整package声明和导入路径实现。拆分能提升可维护性与编译效率,合理使用接口和公共包可避免循环依赖,但需警惕过度拆分导致的认知负担与依赖复杂化,应以清晰职责划分而非文件大小为拆分依据。 在Go语言中,将一个臃肿的 pa…

    2025年12月15日
    000
  • GolangHTTP客户端使用 自定义请求头设置

    在Golang中为HTTP请求添加自定义头,需通过http.NewRequest创建请求对象,再使用req.Header.Set或Add方法设置头部,最后用自定义Client发送请求。示例代码展示了设置X-My-Custom-ID、User-Agent及多值X-Trace-Info的过程,并利用ht…

    2025年12月15日
    000
  • Golang的errgroup包如何帮助管理一组goroutine的错误

    errgroup包通过结合context实现并发任务的错误管理和协同取消,其核心是WithCancel创建的上下文在任一任务出错时自动取消,使其他任务及时退出,从而高效控制并发生命周期。 Golang的 errgroup 包,在我看来,是处理并发任务中错误管理的一个非常优雅且高效的工具。它本质上提供…

    2025年12月15日
    000
  • Golang开发环境在启用Go Modules后GOPATH还需要设置吗

    Go Modules取代GOPATH实现项目独立与版本隔离。它通过go.mod和go.sum确保依赖确定性,支持全局缓存与模块代理,提升构建效率与可维护性,仅在旧项目兼容和全局工具安装时需GOPATH。 其实,在Go Modules大行其道的今天,GOPATH的强制性已经大大降低,甚至可以说,它在大…

    2025年12月15日
    000
  • Golang反射基础概念 reflect包核心原理解析

    Go语言通过reflect包实现反射,可在运行时获取变量的类型(reflect.Type)和值(reflect.Value),支持动态操作数据结构。使用TypeOf和ValueOf分别获取类型与值信息,二者均基于空接口传递。reflect.Value可调用.Type()回溯类型,.Kind()判断底…

    2025年12月15日
    000
  • Golang的go mod edit命令有哪些实用的编辑功能

    go mod edit 是 Go 模块管理的底层工具,可直接精确修改 go.mod 文件,支持模块路径变更、依赖添加/移除、替换规则、版本排除、Go 版本设置等操作,适用于本地开发调试、CI/CD 动态配置及复杂依赖问题处理,弥补 go get 和 go mod tidy 在精细控制上的不足,尤其在…

    2025年12月15日
    000
  • Golang检测指针逃逸 gcflags参数使用方法

    逃逸分析是Go编译器判断变量是否超出函数作用域的过程,若变量逃逸则分配在堆上。通过go build -gcflags=”-m”可查看逃逸信息,如“escapes to heap”表示变量被堆分配,常见于返回局部变量指针或被goroutine捕获等情况,合理使用该机制可优化内存…

    2025年12月15日
    000
  • Golang指针并发安全 原子操作与互斥锁方案

    并发安全的关键是保护指针指向的数据而非指针本身,多goroutine下需防止数据竞争。使用atomic可对简单类型实现高效无锁操作,如原子读写、增减和比较交换,适用于计数器等单一变量场景;涉及复杂结构或多个操作原子性时应选用mutex或RWMutex,确保临界区互斥,读多写少用RWMutex提升性能…

    2025年12月15日
    000
  • Golang指针作为结构体字段的常见应用场景

    指针作为结构体字段可共享数据、减少拷贝、表达可选性并构建复杂结构。1. 多个结构体通过指针引用同一对象实现共享修改;2. 避免大结构体拷贝提升性能;3. 利用nil表示可选字段;4. 实现树、链表等引用结构。 在Go语言中,指针作为结构体字段的使用非常普遍,尤其在需要共享数据、节省内存或实现可变性时…

    2025年12月15日
    000
  • 如何使用Golang反射遍历一个结构体的所有字段和方法

    首先通过reflect.TypeOf和reflect.ValueOf获取结构体类型和值,再利用NumField和Field遍历导出字段,通过NumMethod和Method获取方法,注意仅首字母大写的字段和方法可被反射访问。 在Go语言中,反射(reflection)通过 reflect 包实现,可…

    2025年12月15日
    000
  • Go语言Web应用中的URL重定向最佳实践

    本文详细阐述了在Go语言Web应用中实现高效且符合规范的HTTP重定向策略。通过使用Go%ignore_a_1%的http.Redirect函数,开发者可以优雅地将用户请求从一个URL重定向到另一个,确保浏览器地址栏正确更新,并避免客户端元刷新等非标准方法。文章提供了单次重定向和可复用重定向处理器的…

    2025年12月15日
    000
  • Golang GAE 应用中实现 URL 重定向的最佳实践

    在 Google App Engine (GAE) 上使用 Go 语言开发 Web 应用时,经常需要实现 URL 重定向功能。例如,将旧的 URL 永久性地重定向到新的 URL,以便用户访问旧链接时能够自动跳转到新的页面,同时保持浏览器地址栏中的 URL 正确显示。本文将介绍两种实现 URL 重定向…

    2025年12月15日
    000
  • Go语言HTTP重定向实践:优雅处理URL跳转

    本文旨在深入探讨Go语言Web应用中实现HTTP重定向的最佳实践。我们将重点介绍如何利用http.Redirect函数进行服务器端重定向,以确保用户浏览器地址栏正确更新,并避免使用客户端元刷新。文章将提供一次性重定向和可复用重定向处理器的代码示例,并讨论关键注意事项,帮助开发者构建高效、符合SEO标…

    2025年12月15日
    000

发表回复

登录后才能评论
关注微信