zoukankan      html  css  js  c++  java
  • golang学习笔记---等待 goroutine 完成任务

    Goroutine 是 Golang 中非常有用的功能,但是在使用中我们经常碰到下面的场景:如果希望等待当前的 goroutine 执行完成,然后再接着往下执行,该怎么办?本文尝试介绍这类问题的解决方法。

    没有等待的情况

    让我们运行下面的代码,并关注输出的结果:

    复制代码
    package main
    
    import (
        "time"
        "fmt"
    )
    
    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!
    因为 goroutine 以非阻塞的方式执行,它们会随着程序(主线程)的结束而消亡,所以程序输出字符串 "over!" 就退出了,这可不是我们想要的结果。

    使用 Sleep 函数等待

    要解决上面的问题,最简单、直接的方式就是通过 Sleep 函数死等 goroutine 执行完成:

    func main() {
        go say("hello world")
        time.Sleep(1000 * time.Millisecond)
        fmt.Println("over!")
    }

    运行修改后的程序,结果如下:
    hello world
    hello world
    hello world
    over!
    结果符合预期,但是太 low 了,我们不知道实际执行中应该等待多长时间,所以不能接受这个方案!

    使用 channel

    通过 channel 也可以达到等待 goroutine 结束的目的,运行下面的代码:

    复制代码
    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!")
    }
    复制代码

    输出的结果也是:
    hello world
    hello world
    hello world
    over!
    这种方法的特点是执行多少次 done <- true 就得执行多少次 <-done,所以也不是优雅的解决方式。

    标准答案

    Golang 官方在 sync 包中提供了 WaitGroup 类型来解决这个问题。其文档描述如下:

    A WaitGroup waits for a collection of goroutines to finish. The main goroutine calls Add to set the number of goroutines to wait for. Then each of the goroutines runs and calls Done when finished. At the same time, Wait can be used to block until all goroutines have finished.

    大意为:WaitGroup 用来等待单个或多个 goroutines 执行结束。在主逻辑中使用 WaitGroup 的 Add 方法设置需要等待的 goroutines 的数量。在每个 goroutine 执行的函数中,需要调用 WaitGroup 的 Done 方法。最后在主逻辑中调用 WaitGroup 的 Wait 方法进行阻塞等待,直到所有 goroutine 执行完成。
    使用方法可以总结为下面几点:

    1. 创建一个 WaitGroup 实例,比如名称为:wg
    2. 调用 wg.Add(n),其中 n 是等待的 goroutine 的数量
    3. 在每个 goroutine 运行的函数中执行 defer wg.Done()
    4. 调用 wg.Wait() 阻塞主逻辑

    运行下面的代码:

    复制代码
    package main
    
    import (
        "time"
        "fmt"
        "sync"
    )
    
    func main() {
        var wg sync.WaitGroup
        wg.Add(2)
        say2("hello", &wg)
        say2("world", &wg)
        fmt.Println("over!")
    }
    
    func say2(s string, waitGroup *sync.WaitGroup) {
        defer waitGroup.Done()
    
        for i := 0; i < 3; i++ {
            fmt.Println(s)
        }
    }
    复制代码

    输出的结果如下:
    hello
    hello
    hello
    world
    world
    world
    over!

    下面是一个稍稍真实一点的例子,检查请求网站的返回状态。如果要在收到所有的结果后进一步处理这些返回状态,就需要等待所有的请求结果返回:

    复制代码
    package main
    
    import (
        "fmt"
        "sync"
        "net/http"
    )
    
    func main() {
        var urls = []string{
            "https://www.baidu.com/",
            "https://www.cnblogs.com/",
        }
    
        var wg sync.WaitGroup
    
        for _, url := range urls {
            wg.Add(1)
            go fetch(url, &wg)
        }
    
        wg.Wait()
    }
    
    func fetch(url string, wg *sync.WaitGroup) (string, error) {
    defer wg.Done() resp, err := http.Get(url) if err != nil { fmt.Println(err) return "", err }
    fmt.Println(resp.Status) return resp.Status, nil }
    复制代码

    运行上面的代码,输出的结果如下:
    200 OK
    200 OK

  • 相关阅读:
    【Ubuntu 笔记】翻译 32.1 Job Control
    【Class 学习笔记】 浮点数的储存,运算
    【Ubuntu学习笔记】 安装篇-锐捷、scim、root改密码
    【Ubuntu学习笔记】安装篇-网络配置遇到的问题
    MySQL增删改查
    Spring MVC详解
    SSH和SSM对比总结
    hibernate工作原理
    JavaBean与xml互转的方法详解
    spring MVC如何获取session传值到前台
  • 原文地址:https://www.cnblogs.com/saryli/p/13345904.html
Copyright © 2011-2022 走看看