一篇学会Go并发等待

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

[[411766]]

上节答疑

上一节有读者问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!"

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

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

使用 Sleep 等待

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

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

hello world 
hello world 
hello world 
over! 
  • 1.
  • 2.
  • 3.
  • 4.

结果符合预期,但是太 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!"

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

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

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

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) 
    } 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

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

hello 
hello 
hello 
over! 
world 
world 
world 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

小心缺陷

简短的例子,注意循环传入的变量用中间变量替代,防止闭包 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() 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

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



  • 1.
  • 2.

父协程与子协程是并发的。父协程上的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() 

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

输出



  • 1.
  • 2.

要留意 range 中的value有可能出现 1.7.3 有可能会遇到的坑!

责任编辑:武晓燕 来源: 机智的程序员小熊
相关推荐

2022-05-17 08:02:55

GoTryLock模式

2022-06-09 08:41:17

Go网络库Gnet

2021-06-24 06:35:00

Go语言进程

2021-03-24 06:06:13

Go并发编程Singlefligh

2021-11-15 10:29:39

Go语言类型

2023-12-05 07:14:27

AIGo

2024-05-10 08:15:32

go语言反射机制

2022-01-02 08:43:46

Python

2022-02-07 11:01:23

ZooKeeper

2021-07-06 08:59:18

抽象工厂模式

2021-05-11 08:54:59

建造者模式设计

2021-07-02 09:45:29

MySQL InnoDB数据

2023-01-03 08:31:54

Spring读取器配置

2021-07-05 22:11:38

MySQL体系架构

2022-08-26 09:29:01

Kubernetes策略Master

2023-11-28 08:29:31

Rust内存布局

2022-08-23 08:00:59

磁盘性能网络

2022-04-12 08:30:52

回调函数代码调试

2021-10-27 09:59:35

存储

2023-11-01 09:07:01

Spring装配源码
点赞
收藏

51CTO技术栈公众号