Golang并发等待

简介: Golang并发等待

上节答疑


上一节有读者问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 执行完成再接着往下执行,该怎么办?


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 有可能会遇到的坑!(位于电子书golang.coding3min.com中)


引用


Golang 入门 : 等待 goroutine 完成任务


相关文章
|
6月前
|
Go
浅谈Golang并发控制WaitGroup
浅谈Golang并发控制WaitGroup
56 0
|
存储 安全 编译器
Golang 语言中 map 的键值类型选择,它是并发安全的吗?
Golang 语言中 map 的键值类型选择,它是并发安全的吗?
70 0
|
2月前
|
安全 Go
Golang语言goroutine协程并发安全及锁机制
这篇文章是关于Go语言中多协程操作同一数据问题、互斥锁Mutex和读写互斥锁RWMutex的详细介绍及使用案例,涵盖了如何使用这些同步原语来解决并发访问共享资源时的数据安全问题。
86 4
|
6月前
|
Go
深度探讨 Golang 中并发发送 HTTP 请求的最佳技术
深度探讨 Golang 中并发发送 HTTP 请求的最佳技术
121 4
|
6月前
|
存储 缓存 安全
Golang深入浅出之-Go语言中的并发安全容器:sync.Map与sync.Pool
Go语言中的`sync.Map`和`sync.Pool`是并发安全的容器。`sync.Map`提供并发安全的键值对存储,适合快速读取和少写入的情况。注意不要直接遍历Map,应使用`Range`方法。`sync.Pool`是对象池,用于缓存可重用对象,减少内存分配。使用时需注意对象生命周期管理和容量控制。在多goroutine环境下,这两个容器能提高性能和稳定性,但需根据场景谨慎使用,避免不当操作导致的问题。
192 7
|
6月前
|
安全 Go 开发者
Golang深入浅出之-Go语言中的CSP模型:深入理解并发哲学
【5月更文挑战第2天】Go语言的并发编程基于CSP模型,强调通过通信共享内存。核心概念是goroutines(轻量级线程)和channels(用于goroutines间安全数据传输)。常见问题包括数据竞争、死锁和goroutine管理。避免策略包括使用同步原语、复用channel和控制并发。示例展示了如何使用channel和`sync.WaitGroup`避免死锁。理解并发原则和正确应用CSP模型是编写高效安全并发程序的关键。
163 7
|
6月前
|
安全 Go
Golang深入浅出之-Go语言中的并发安全队列:实现与应用
【5月更文挑战第3天】本文探讨了Go语言中的并发安全队列,它是构建高性能并发系统的基础。文章介绍了两种实现方法:1) 使用`sync.Mutex`保护的简单队列,通过加锁解锁确保数据一致性;2) 使用通道(Channel)实现无锁队列,天生并发安全。同时,文中列举了并发编程中常见的死锁、数据竞争和通道阻塞问题,并给出了避免这些问题的策略,如明确锁边界、使用带缓冲通道、优雅处理关闭以及利用Go标准库。
445 5
|
6月前
|
安全 Go 开发者
Golang深入浅出之-Go语言中的CSP模型:深入理解并发哲学
【5月更文挑战第1天】Go语言基于CSP理论,借助goroutines和channels实现独特的并发模型。Goroutine是轻量级线程,通过`go`关键字启动,而channels提供安全的通信机制。文章讨论了数据竞争、死锁和goroutine泄漏等问题及其避免方法,并提供了一个生产者消费者模型的代码示例。理解CSP和妥善处理并发问题对于编写高效、可靠的Go程序至关重要。
147 2
|
6月前
|
设计模式 Go 调度
Golang深入浅出之-Go语言中的并发模式:Pipeline、Worker Pool等
【5月更文挑战第1天】Go语言并发模拟能力强大,Pipeline和Worker Pool是常用设计模式。Pipeline通过多阶段处理实现高效并行,常见问题包括数据竞争和死锁,可借助通道和`select`避免。Worker Pool控制并发数,防止资源消耗,需注意任务分配不均和goroutine泄露,使用缓冲通道和`sync.WaitGroup`解决。理解和实践这些模式是提升Go并发性能的关键。
79 2
|
Go
Golang 语言怎么控制并发 goroutine?
Golang 语言怎么控制并发 goroutine?
40 0