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