Skip to main content
 首页 » 编程设计

Golang 同步等待组(WaitGroup)

2022年07月19日144傻小

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同步等待组以及如何使用它实现高性能应用。


本文参考链接:https://blog.csdn.net/neweastsun/article/details/106971629
阅读延展