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