前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go并发等待

Go并发等待

作者头像
机智的程序员小熊
发布2021-07-20 11:52:27
1.5K0
发布2021-07-20 11:52:27
举报
文章被收录于专栏:技术面面观技术面面观

上节答疑

上一节有读者问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/ ”

简介

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

代码语言:javascript
复制
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 等待

代码语言:javascript
复制
func main() {
    go say("hello world")
    time.Sleep(time.Second*1)
    fmt.Println("over!")
}

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

代码语言:javascript
复制
hello world
hello world
hello world
over!

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

发送信号

代码语言:javascript
复制
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 执行完成。
代码语言:javascript
复制
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)
    }
}

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

代码语言:javascript
复制
hello
hello
hello
over!
world
world
world

小心缺陷

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

代码语言:javascript
复制
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()
}

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

代码语言:javascript
复制
b
b

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

解决方法当作参数传入

代码语言:javascript
复制
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()
}

输出

代码语言:javascript
复制
b
a
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-07-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 机智的程序员小熊 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 上节答疑
  • Golang并发等待
  • 简介
  • 使用 Sleep 等待
  • 发送信号
  • WaitGroup
  • 小心缺陷
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档