您现在的位置是:亿华云 > 数据库

一篇学会Go并发等待

亿华云2025-10-03 02:13:21【数据库】1人已围观

简介上节答疑上一节有读者问goroutine stack size一般是多大,我进行了详细的查询关于 goroutine stack size(栈内存大小) 官方的文档 中所述,1.2 之前最小是4kb,

上节答疑

上一节有读者问goroutine stack size一般是篇学多大,我进行了详细的篇学查询

关于 goroutine stack size(栈内存大小) 官方的文档 中所述,1.2 之前最小是篇学4kb,在1.2 变成8kb,篇学并且可以使用SetMaxStack 设置栈最大大小。篇学

在 runtime/debug 包能控制最大的篇学单个 goroutine 的堆栈的大小。在 64 位系统上默认为 1GB,篇学在 32 位系统上默认为 250MB。篇学

因为每个goroutine需要能够运行,篇学所以它们都有自己的篇学栈。假如每个goroutine分配固定栈大小并且不能增长,篇学太小则会导致溢出,篇学太大又会浪费空间,篇学无法存在许多的篇学goroutine。

所以在1.3版本中,篇学改为了 Contiguous stack( 连续栈 ),为了解决这个问题,goroutine可以初始时只给栈分配很小的亿华云空间(8KB),然后随着使用过程中的需要自动地增长。这就是为什么Go可以开千千万万个goroutine而不会耗尽内存。

1.4 版本 goroutine 堆栈从 8Kb 减少到 2Kb

Golang并发等待

本节源码位置 https://github.com/golang-minibear2333/golang/blob/master/4.concurrent/goroutine-wait/”

简介

goroutine 是 Golang 中非常有用的功能,有时候 goroutine 没执行完函数就返回了,如果希望等待当前的 goroutine 执行完成再接着往下执行,该怎么办?

func say(s string) {      for i := 0; i < 3; i++ {          time.Sleep(100 * time.Millisecond)         fmt.Println(s)     } } func main() {      go say("hello world")     fmt.Println("over!") } 

输出 over! , 主线程没有等待

使用 Sleep 等待

func main() {      go say("hello world")     time.Sleep(time.Second*1)     fmt.Println("over!") } 

运行修改后的程序,结果如下:

hello world hello world hello world over! 

结果符合预期,但是太 low 了,我们不知道实际执行中应该等待多长时间,所以不能接受这个方案!

发送信号

func main() {      done := make(chan bool)     go func() {          for i := 0; i < 3; i++ {              time.Sleep(100 * time.Millisecond)             fmt.Println("hello world")         }         done <- true     }()     <-done     fmt.Println("over!") } 

输出的结果和上面相同,也符合预期

这种方式不能处理多个协程,所以也不是优雅的解决方式。

WaitGroup

Golang 官方在 sync 包中提供了 WaitGroup 类型可以解决这个问题。其文档描述如下:

使用方法可以总结为下面几点:

在父协程中创建一个 WaitGroup 实例,比如名称为:wg 调用 wg.Add(n) ,源码下载其中 n 是等待的 goroutine 的数量 在每个 goroutine 运行的函数中执行 defer wg.Done() 调用 wg.Wait() 阻塞主逻辑 直到所有 goroutine 执行完成。 func main() {      var wg sync.WaitGroup     wg.Add(2)     go say2("hello", &wg)     go say2("world", &wg)     fmt.Println("over!")     wg.Wait() } func say2(s string, waitGroup *sync.WaitGroup) {      defer waitGroup.Done()     for i := 0; i < 3; i++ {          fmt.Println(s)     } } 

输出,注意顺序混乱是因为并发执行

hello hello hello over! world world world 

小心缺陷

简短的例子,注意循环传入的变量用中间变量替代,防止闭包 bug

func errFunc() {   var wg sync.WaitGroup  sList := []string{ "a", "b"}  wg.Add(len(sList))  for _, d := range sList {    go func() {     defer wg.Done()    fmt.Println(d)   }()  }  wg.Wait() } 

输出,可以发现全部变成了最后一个

b b 

父协程与子协程是并发的。父协程上的for循环瞬间执行完了,内部的协程使用的是d最后的值,这就是闭包问题。

解决方法当作参数传入

func correctFunc() {   var wg sync.WaitGroup  sList := []string{ "a", "b"}  wg.Add(len(sList))  for _, d := range sList {    go func(str string) {     defer wg.Done()    fmt.Println(str)   }(d)  }  wg.Wait() } 

输出

b a 

要留意 range 中的value有可能出现 1.7.3 有可能会遇到的站群服务器坑!

很赞哦!(4919)