Go并发控制WaitGroup的使用场景分析
Go语言的并发模型是通过goroutine和channel实现的。goroutine是轻量级线程,可以在同一进程的多个线程之间切换执行。channel提供了goroutine之间的通信和同步机制。在使用goroutine时,我们很常用到sync.WaitGroup来控制并发。本文将详细讲解WaitGroup的使用场景和用法。
WaitGroup的基本用法
在Go语言中,sync.WaitGroup是一个计数器,它可以用来等待多个goroutine执行完毕。WaitGroup内部有一个计数器,最初为0,可以通过sync.WaitGroup的方法Add来设置要等待的goroutine的个数,每个goroutine在执行完后调用Done方法通知WaitGroup,计数器会减1。当计数器清零后,所有的等待在Wait方法上的goroutine都会被唤醒执行。
例如,我们有3个任务需要并发执行,每个任务执行时间不同,代码如下:
package main
import (
"fmt"
"sync"
"time"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
time.Sleep(time.Second)
fmt.Printf("Worker %d done\n", id)
}
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
wg.Wait()
fmt.Println("All workers done.")
}
程序的核心是worker函数,每个worker函数作为一个goroutine并发执行。在main函数中,我们先创建了一个WaitGroup实例,然后使用wg.Add(1)增加了3次计数器,然后启动3个goroutine并传入WaitGroup,每个worker函数执行完毕后调用wg.Done()通知WaitGroup计数器减1。当计数器清零后,执行wg.Wait()的goroutine(本例即main函数)会被唤醒执行。
输出结果:
Worker 1 starting
Worker 2 starting
Worker 3 starting
Worker 3 done
Worker 1 done
Worker 2 done
All workers done.
WaitGroup的高级用法
并发请求数据
另外一个常用场景是并发请求数据。比如我们要从多个API接口中同时请求数据,然后汇总结果。
package main
import (
"fmt"
"sync"
"time"
)
// 模拟API接口
func api(i int) int {
time.Sleep(time.Second)
return i * 2
}
func fetch(id int, wg *sync.WaitGroup, result chan int) {
defer wg.Done()
r := api(id)
result <- r
}
func main() {
var wg sync.WaitGroup
result := make(chan int, 3)
for i := 1; i <= 3; i++ {
wg.Add(1)
go fetch(i, &wg, result)
}
wg.Wait()
close(result)
sum := 0
for r := range result {
sum += r
}
fmt.Println("Total:", sum)
}
在本例中,我们模拟了一个API请求,每个请求需要1秒的处理时间。fetch函数作为goroutine并发调用api函数获取数据。当api请求完成后,fetch会将结果写入result channel中。main函数等待所有的fetch goroutine执行完毕后,关闭channel。最后,从channel中读取所有的结果并计算总和。
输出结果:
Total: 12
并发请求大量数据
另一个常见的场景是并发请求大量数据。在本场景中,我们会启动若干个goroutine并发请求数据,并将每个goroutine的结果计算起来。由于数据量非常大,无法一次性处理完毕,因此需要把数据按照一定的范围分成多个部分进行处理,并把多个部分的结果合并起来。
package main
import (
"fmt"
"sync"
)
const (
dataSize = 1000000
goroutine = 100
)
// 模拟获取数据
func getData(start, end int) []int {
data := make([]int, end-start)
for i := 0; i < end-start; i++ {
data[i] = start + i
}
return data
}
// 计算结果
func sum(data []int, result chan int, wg *sync.WaitGroup) {
s := 0
for _, v := range data {
s += v
}
result <- s
wg.Done()
}
func main() {
var wg sync.WaitGroup
result := make(chan int, goroutine)
for i := 0; i < goroutine; i++ {
wg.Add(1)
start := i * (dataSize / goroutine)
end := (i + 1) * (dataSize / goroutine)
data := getData(start, end)
go sum(data, result, &wg)
}
wg.Wait()
close(result)
sum := 0
for r := range result {
sum += r
}
fmt.Println("Total:", sum)
}
在本例中,我们模拟了一个包含100万个数据的序列。我们将这个序列分成100个部分,每个部分10000个数据。然后,我们使用100个goroutine并发地计算每个部分的数据,并将结果写入result channel。main函数会等待所有goroutine执行完毕后,关闭channel并计算所有结果的总和。
输出结果:
Total: 499999500000
总结
本文介绍了sync.WaitGroup的基本使用方法和高级使用方法。在实际开发中,WaitGroup可以帮助我们控制并发执行的goroutine,通过等待所有goroutine执行完毕后再执行后续操作,可以避免程序运行出现不可预期的结果。在实际使用中,需要根据具体的需求选择合适的方式和参数设置,并注意避免死锁等问题。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Go并发控制WaitGroup的使用场景分析 - Python技术站