Golang 同步等待组(WaitGroup)

Golang 同步等待组(WaitGroup)

如果你正在学习Go的高性能并发应用开发,那么了解同步等待组至关重要。本文带你认识同步等待组并通过示例进行说明。

1. 同步等待组(WaitGroup)

让我们直入主题,说明是同步等待组(WaitGroup),能够解决什么问题。

在实际使用Go协程实现并行应用时,可能会遇到这样场景:需要阻塞部分代码执行,直到其他协程成功执行之后才继续执行。
示例代码:

package main

import "fmt"

func myFunc() {
    fmt.Println("Inside my goroutine")
}

func main() {
    fmt.Println("Hello World")
    go myFunc()
    fmt.Println("Finished Execution")
}

程序首先打印"Hello World",接着启动协程,最后打印"Finished Execution"。
但我们执行程序结果并不是我们预期的结果,协程内的信息"Inside my goroutine"并没有出现。这是因为main在协程执行之前以及结束,所以协程中的逻辑并未执行。

如何解决————同步等待组(WaitGroups)

同步等待组(WaitGroups)就是要解决这类问题,阻塞应用直到同步等待组中的所有协程都成功执行。
首先调用同步等待组的Add(1)方法,设置需要等待协程数量, 然后再协程内部调用Done() 方法表明协程执行结束。

注意,需要确保再执行协程之前调用Add(1)方法。

2. 示例

掌握了一些基本概念后,下面通过示例展示如何通过同步等待组解决上述问题:

package main

import (
    "fmt"
    "sync"
)

func myFunc(waitgroup *sync.WaitGroup) {
    fmt.Println("Inside my goroutine")
    waitgroup.Done()
}

func main() {
    fmt.Println("Hello World")

    var waitgroup sync.WaitGroup
    waitgroup.Add(1)
    go myFunc(&waitgroup)
    waitgroup.Wait()

    fmt.Println("Finished Execution")
}

我们看到首先实例化sync.WaitGroup,然后再执行协程之前调用Add(1)方法。修改原来函数增加*sync.WaitGroup参数,并在函数内部成功完成任务后调用一次Done方法。最后调用waitgroup.Wait()方法阻塞main函数执行,直到同步等待组中的协程成功执行完成。

下面再次运行程序输出结果如下:

Hello World
Inside my goroutine
Finished Execution
  • 匿名函数

我们也可以使用匿名函数实现相同功能。对于协程内部业务不复杂,匿名函数会让程序更简洁:

package main

import (
    "fmt"
    "sync"
)

func main() {
    fmt.Println("Hello World")

    var waitgroup sync.WaitGroup
    waitgroup.Add(1)
    go func() {
        fmt.Println("Inside my goroutine")
        waitgroup.Done()
    }()
    waitgroup.Wait()

    fmt.Println("Finished Execution")
}

输出结果一样。对于稍微复杂的逻辑,可能需要给匿名函数传入参数,例如需要传入url参数:

go func(url string) {
  fmt.Println(url)
}(url)

只是写法有点差异,其他都差不多。

3. 实战应用

在示例生产应用程序中,任务是创建一个API,该API与大量其他API交互,并将结果聚合到一个响应中。每个API调用大约花费2-3秒时间来返回响应,由于需要调用的API数量太多,不可能同步地进行此操作。

为了实现该功能,需要使用协程异步执行这些请求。

package main

import (
    "fmt"
    "log"
    "net/http"
)

var urls = []string{
    "https://baidu.com",
    "https://csdn.net",
    "https://qq.com",
}

func fetch(url string) {
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(resp.Status)
}

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Println("HomePage Endpoint Hit")
    for _, url := range urls {
        go fetch(url)
    }
    fmt.Println("Returning Response")
    fmt.Fprintf(w, "All Responses Received")
}

func handleRequests() {
    http.HandleFunc("/", homePage)
    log.Fatal(http.ListenAndServe(":8081", nil))
}

func main() {
    handleRequests()
}

然而当我开始使用这种策略时,我注意到我对任何API调用都在协程有机会完成填充结果之前返回。这时需要使用同步等待组重新实现该功能,通过使用WaitGroup,我可以有效地修复这种异常行为,并且只在所有goroutines完成后返回结果。

package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
)

var urls = []string{
    "https://baidu.com",
    "https://csdn.net",
    "https://qq.com",
}

func fetch(url string, wg *sync.WaitGroup) (string, error) {
    resp, err := http.Get(url)
    if err != nil {
        fmt.Println(err)
        return "", err
    }
    wg.Done()
    fmt.Println(resp.Status)
    return resp.Status, nil
}

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Println("HomePage Endpoint Hit")
    var wg sync.WaitGroup

    for _, url := range urls {
        wg.Add(1)
        go fetch(url, &wg)
    }

    wg.Wait()
    fmt.Println("Returning Response")
    fmt.Fprintf(w, "Responses")
}

func handleRequests() {
    http.HandleFunc("/", homePage)
    log.Fatal(http.ListenAndServe(":8081", nil))
}

func main() {
    handleRequests()
}

现在我们增加同步等待锁,它将对所有URL执行HTTP GET请求,一旦执行完毕返回给调用客户端。输出结果为:

HomePage Endpoint Hit
200 OK
200 OK
200 OK
Returning Response

处理这类问题的方法不止一种,通过使用Golang的通道也可以实现类似功能。

4. 总结

本文学习了什么是Golang同步等待组以及如何使用它实现高性能应用。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值