优雅地等待子协程执行完毕

goroutine模拟了线程级别的返场的能力,但它的执行需要主协程给机会。一般的作法用sleep,chan阻塞,看起来让人不爽,本文介绍sync.WaitGroup 类型结合 defer 的特性,给出优雅的解决方案。

缘起

下面这段代码众所周知不会打招呼

....

func main(){
    go sayHi(){
        fmt.Println("say hello......")
    }()
    fmt.Println("main groutine....")
}

等待

上述代码不会讲hello,在于主协程main无等待(阻塞),子协程sayHi没有露脸的机会。
为了让子协程sayHai上场,通常在主协程末了加这么一句,让它睡会儿

time.Sleep(1e9)

但想想不科学,如果子协程在它睡期间,没能完成任务,超时了,子协程仍然打不了招呼。又或者子协程完成了,主协程还在睡,岂不是主线程不作为?睡的时间多久怎样才合理...

通道

用通道可解决阻塞时间合理性质疑。
若启用了多个子协程,可以这样实现主协程等待子协程执行完毕并退出的:声明一个和子协程数量一致的通道数组,然后为每个子协程分配一个通道元素,在子协程执行完毕时向对应的通道发送数据;然后在主协程中,依次读取这些通道接收子协程发送的数据,只有所有通道都接收到数据才会退出主协程。
代码看起来像这样

chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
    chs[i] = make(chan int)
    go add(1, i, chs[i])
}
for _, ch := range chs {
    <- ch
}

感觉这样的实现有点蹩脚,不够优雅,于是 sync.waitGroup 入场了

WaitGroup 类型

sync.WaitGroup 类型是并发安全的,提供了以下三个方法:

func (wg *WaitGroup) Add(delta int)
func (wg *WaitGroup) Done()
func (wg *WaitGroup) Wait()
  • Add:WaitGroup 类型有一个计数器,默认值是0,通常通过个方法来标记需要等待的子协程数量
  • Done:当某个子协程执行完毕后,可以通过 Done 方法标记已完成,常用 defer 语句来调用
  • Wait 阻塞当前协程,直到对应 WaitGroup 类型实例的计数器值归零

代码

至此,相信你已经明白了,可组合使用 sync.WaitGroup 类型提供的方法,来替代之前通道中等待子协程执行完毕。
优雅等待子协程执行完毕代码如下

package main

import (
    "fmt"
    "sync"
)

func add_num(a, b int, done func()) {
    defer done()
    c := a + b
    fmt.Printf("%d + %d = %d\n", a, b, c)
}

func main() {
    var wg sync.WaitGroup
    wg.Add(10)
    for i := 0; i < 10; i++ {
        go add_num(i, 1, wg.Done)
    }
    wg.Wait()
}

让当前协程永久阻塞

其实runtime 运行时默认是只开启一个逻辑处理器,如果不想用通道,也不阻塞,可以考虑设置多个逻辑处理器。或者干脆用select让当前主协程永久阻塞, 像下面这样

package main

import "runtime"

func DoSomething() {
    for {
        // 做点什么...

        runtime.Gosched() // 防止本协程霸占CPU不放
    }
}

func main() {
    go DoSomething()
    go DoSomething()
    select{}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
pardon110
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
开发者 @ 社科大
文章
134
粉丝
24
喜欢
101
收藏
55
排名:106
访问:8.9 万
私信
所有博文
社区赞助商