Go 语言并发请求多个 API:Goroutine 与 WaitGroup 实战
本文介绍如何使用 Go 语言的 Goroutine 和 sync.WaitGroup 并发执行多个 HTTP 请求。通过示例代码对比串行与并行调用的差异,讲解 Channel 结果收集与等待组配合使用方法,帮助开发者掌握高效并发处理外部 API 的技巧。
Golang 并发原理简述
Golang 的高性能在很大程度上归功于其轻量级的并发抽象。与 Java 将线程直接映射到操作系统线程不同,Go 使用自身的调度器管理 Goroutine。这种机制使得在单个操作系统线程上调度成千上万个 Goroutine 成为可能。当一个 Goroutine 被阻塞(如等待 I/O)时,调度器会将其挂起并切换到另一个就绪的 Goroutine,从而保持 CPU 核心的高利用率。
串行调用:基础场景
为了理解并发的价值,我们首先看一个串行调用的简单示例。该程序遍历一组 GitHub 用户 URL 并检查连接状态。由于使用了 for 循环逐个执行 http.Get,所有请求必须在前一个完成后才能开始,效率较低。
// 串行调用示例
package main
import (
"fmt"
"net/http"
)
func main() {
links := []string{
"https://github.com/fabpot",
"https://github.com/andrew",
"https://github.com/taylorotwell",
}
checkUrls(links)
}
func checkUrls(urls []string) {
for _, link := range urls {
checkUrl(link)
}
}
func checkUrl(url string) {
_, err := http.Get(url)
if err != nil {
fmt.Println("Failed:", url)
} else {
fmt.Println("Success:", url)
}
}
高性能并发调用实现
要实现并发调用,核心在于三个要素:启动 Goroutine、使用 Channel 传递结果、以及利用 WaitGroup 同步等待所有任务完成。
第1步:初始化并发工具
在并发函数中,我们需要创建两个关键结构:
c := make(chan string):定义一个字符串类型的 Channel,用于从子 Goroutine 向主程序传递结果。发送操作使用<-符号,例如c <- result。var wg sync.WaitGroup:引入标准库sync中的 WaitGroup,用于跟踪正在运行的任务数量,防止主程序过早退出。
第2步:启动 Goroutine 并计数
在遍历 URL 的请求循环中,不再直接调用处理函数,而是使用 go 关键字启动新的 Goroutine。同时,每次启动前必须调用 wg.Add(1) 以递增等待计数器,确保主程序知道还有多少任务待完成。
func checkUrls(urls []string) {
c := make(chan string)
var wg sync.WaitGroup
// 启动携程并记录任务数
for _, link := range urls {
wg.Add(1)
go checkUrl(link, c, &wg)
}
...
}
第3步:同步等待并关闭通道
这是一个容易出错的环节。如果直接在主 Goroutine 中读取 Channel,而子任务尚未结束,主程序可能会阻塞或提前退出。解决方案是开启一个新的 Goroutine 专门负责等待:
- 在该 Goroutine 中调用
wg.Wait(),这将阻塞直到所有任务的计数器归零。 - 当所有任务完成后,立即调用
close(c)关闭 Channel。
注意:如果不关闭 Channel,后续的范围循环(range channel)会因为找不到数据且未收到结束信号而死锁或无限期阻塞。
// 独立携程监控等待组并关闭通道
go func() {
wg.Wait()
close(c)
}()
第4步:接收结果
最后,在主 Goroutine 中使用 range 循环读取 Channel。只要 Channel 未关闭且有数据流入,循环就会持续运行。一旦 Channel 关闭且数据取完,循环自然结束。
for msg := range c {
fmt.Println(msg)
}
完整代码整合
将上述步骤结合,即得到完整的并发调用逻辑。每个子任务在处理完毕后,需通过 defer wg.Done() 确保计数器正确递减。
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
links := []string{
"https://github.com/fabpot",
"https://github.com/andrew",
"https://github.com/taylorotwell",
"https://github.com/egoist",
"https://github.com/HugoGiraudel",
}
checkUrls(links)
}
func checkUrls(urls []string) {
c := make(chan string)
var wg sync.WaitGroup
for _, link := range urls {
wg.Add(1) // 告诉 WaitGroup 有一个待处理的操作
go checkUrl(link, c, &wg)
}
go func() {
wg.Wait() // 阻塞直到所有 wg.Done() 执行完毕
close(c) // 必须关闭通道,否则 for range 无法退出
}()
// 循环读取结果,通道关闭且无数据后退出
for msg := range c {
fmt.Println(msg)
}
}
func checkUrl(url string, c chan string, wg *sync.WaitGroup) {
defer (*wg).Done()
_, err := http.Get(url)
if err != nil {
c <- "Failed reaching:" + url
} else {
c <- "Success reaching:" + url
}
}
以上就是 GoRoutines 高性能同时进行多个 API 调用实现的详细内容,更多关于 GoRoutines 多个 API 同时调用的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













