Go语言中常用的并发模式
目录
1. 工作池模式(Worker Pool Pattern)
概述:
工作池模式用于处理大量任务,它通过创建一组固定数量的协程(workers)来从任务队列中取出任务并执行。这种模式避免了为每个任务单独启动一个协程所带来的开销,并且可以根据系统的处理能力和资源限制调整并发度。
优点:
- 高效利用资源:可以控制并发任务的数量,防止过多的goroutine造成系统负担。
- 灵活性:能够根据需要动态调整worker的数量。
- 易于管理:简化了对大量任务的管理和调度。
实现示例:
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
time.Sleep(time.Second) // 模拟耗时操作
results <- j * 2
fmt.Printf("Worker %d finished job %d\n", id, j)
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// 启动3个工作者(worker)
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}
// 发送任务
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// 收集结果
for a := 1; a <= numJobs; a++ {
<-results
}
}
2. 扇出扇入模式(Fan-Out Fan-In Pattern)
概述:
扇出扇入模式涉及将任务分发给多个协程并行处理(扇出),然后收集所有协程的结果(扇入)。此模式非常适合于需要同时从多个数据源获取数据并最终合并结果的情况,如网络爬虫或多源数据聚合。
优点:
- 提高吞吐量:充分利用多核CPU的优势,加速任务完成。
- 简化逻辑:分离任务分配与结果收集的逻辑,使代码更清晰易懂。
实现示例:
func main() {
in := gen(2, 3, 4, 5) // 扇出
c1 := sq(in)
c2 := sq(in)
// 扇入
for n := range merge(c1, c2) {
fmt.Println(n) // 结果顺序不固定,可能是4, 9, 16, 25等
}
}
// ...省略gen、sq和merge函数定义...
3. 管道模式(Pipeline Pattern)
概述:
管道模式模仿了工厂流水线的工作流程,数据流经一系列阶段,在每个阶段都被处理或转换后传递给下一个阶段。该模式适用于需要连续处理的数据流场景,例如日志处理、ETL过程等。
优点:
- 模块化设计:每个阶段独立运作,便于维护和扩展。
- 高效性:各个阶段可以并行运行,提升了整体效率。
实现示例:
func main() {
naturals := make(chan int)
squares := make(chan int)
// 计数器阶段
go func() {
for x := 0; x < 10; x++ {
naturals <- x
}
close(naturals)
}()
// 平方计算阶段
go func() {
for x := range naturals {
squares <- x * x
}
close(squares)
}()
// 输出阶段
for x := range squares {
fmt.Println(x)
}
}
4. 使用上下文(Context)进行取消操作
概述:
context
包提供了优雅地管理协程生命周期的方法,特别是对于长时间运行的操作来说非常重要。它可以用来传播取消信号、设置超时或者传递请求范围内的值。
优点:
- 资源保护:及时终止不再需要的任务,防止资源泄漏。
- 灵活性:可以通过不同的方式(如超时、手动取消)来控制任务的执行周期。
实现示例:
func main() {
ctx, cancel := context.WithCancel(context.Background())
go func() {
time.Sleep(2 * time.Second)
cancel() // 2秒后取消上下文
}()
select {
case <-time.After(5 * time.Second):
fmt.Println("Operation completed")
case <-ctx.Done():
fmt.Println("Operation canceled")
}
}
5. 错误组模式(ErrGroup Pattern)
概述:
errgroup
包简化了等待一组协程结束并收集它们可能产生的错误信息的过程。这对于确保并发任务要么全部成功,要么全部失败非常有用,特别是在需要事务一致性的场合。
优点:
- 简化错误处理:统一处理并发任务中的错误,减少复杂度。
- 可靠性:保证所有任务都正确完成或全部回滚。
实现示例:
func main() {
var g errgroup.Group
urls := []string{
"https://golang.org",
"https://google.com",
"https://badhost", // 这个URL将会引发问题
}
for _, url := range urls {
url := url // 避免闭包捕获问题
g.Go(func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
fmt.Println("Fetched:", url)
return nil
})
}
if err := g.Wait(); err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Successfully fetched all URLs")
}
}
Do not communicate by sharing memory; instead, share memory by communicating.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)