编程 深入理解 Go 并发操作:通过示例讲解 sync.WaitGroup 和 Context 的使用

2024-11-19 05:04:11 +0800 CST views 1287

深入理解 Go 并发操作:通过示例讲解 sync.WaitGroup 和 Context 的使用

在 Go 语言中,并发编程是一个重要的特性。当一个任务可以被拆分为多个相互独立的子任务,并且这些子任务没有先后执行顺序的限制,需要等到所有子任务执行完毕后再进行下一步处理时,适合使用 sync.WaitGroup

然而,虽然 sync.WaitGroup 使用起来相对简单,但如果不小心,可能会踩到一些坑。本文将通过示例深入讲解如何正确使用 sync.WaitGroup,并介绍在更复杂的并发场景下如何结合 context 来处理任务取消。

正确使用 sync.WaitGroup

假设有一个任务需要执行 3 个子任务,可以使用 sync.WaitGroup 来等待所有子任务完成:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    wg.Add(3)

    go handleTask1(&wg)
    go handleTask2(&wg)
    go handleTask3(&wg)

    wg.Wait()

    fmt.Println("所有任务执行完毕.")
}

func handleTask1(wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("执行任务 1")
}

func handleTask2(wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("执行任务 2")
}

func handleTask3(wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("执行任务 3")
}

执行输出:

执行任务 1
执行任务 3
执行任务 2
所有任务执行完毕.

注意,任务的执行顺序可能会有所不同,因为 Goroutine 是并发运行的。

sync.WaitGroup 使用注意事项

1. 传递指针

正确做法:

go handleTask1(&wg)

错误做法:

go handleTask1(wg)

在启动子任务时,必须传递 *sync.WaitGroup 的指针类型。如果传值,会导致每个子任务都有自己的副本,无法正确同步。

2. 确保 wg.Add() 在启动 Goroutine 之前执行

错误示例:

var wg sync.WaitGroup

go handleTask1(&wg)

wg.Wait()

// ...

func handleTask1(wg *sync.WaitGroup) {
    wg.Add(1)
    defer wg.Done()
    fmt.Println("执行任务 1")
}

在上述代码中,wg.Add(1) 在子 Goroutine 内部执行,但主 Goroutine 已经调用了 wg.Wait(),这可能导致主 Goroutine 提前结束。应当确保 wg.Add() 在启动 Goroutine 之前执行。

3. 保持计数器一致

注意 wg.Add()wg.Done() 的调用次数应当一致。wg.Done() 实际上等价于 wg.Add(-1)

更复杂的场景:任务失败时取消其他任务

sync.WaitGroup 适用于等待所有子任务完成的场景,但如果需要在某个子任务失败时,立即取消其他正在执行的子任务,就需要结合 context 来实现。

使用 sync.WaitGroup 和 Context

以下示例展示了如何在一个子任务失败时,取消其他所有子任务:

package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

func handleTask(ctx context.Context, taskID int, wg *sync.WaitGroup, cancel context.CancelFunc) {
    defer wg.Done()

    fmt.Printf("任务 %d 正在处理...\n", taskID)

    // 模拟任务处理,任务 1 将模拟失败
    if taskID == 1 {
        fmt.Printf("任务 %d 失败\n", taskID)
        cancel() // 取消上下文,通知其他任务停止
        return
    }

    // 检查上下文是否已取消
    select {
    case <-ctx.Done():
        fmt.Printf("任务 %d 已被取消\n", taskID)
        return
    default:
        // 模拟耗时操作
        time.Sleep(3 * time.Second)
        fmt.Printf("任务 %d 成功完成\n", taskID)
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    var wg sync.WaitGroup

    wg.Add(3)
    go handleTask(ctx, 1, &wg, cancel)
    go handleTask(ctx, 2, &wg, cancel)
    go handleTask(ctx, 3, &wg, cancel)

    wg.Wait()
    fmt.Println("所有任务已结束")
}

输出示例:

任务 1 正在处理...
任务 2 正在处理...
任务 3 正在处理...
任务 1 失败
任务 2 已被取消
任务 3 已被取消
所有任务已结束

解释

  1. 任务 1 开始处理:

    • 输出 任务 1 正在处理...
    • 由于 taskID == 1,模拟任务失败,输出 任务 1 失败
    • 调用 cancel(),取消上下文 ctx,通知其他任务停止。
  2. 任务 2 和任务 3 开始处理:

    • 输出 任务 2 正在处理...任务 3 正在处理...
    • 检查上下文 ctx,发现已被取消。
    • 输出 任务 2 已被取消任务 3 已被取消,然后退出。
  3. 等待所有任务结束:

    • 主 Goroutine 调用 wg.Wait(),等待所有子任务完成。
    • 所有子任务结束后,输出 所有任务已结束

通过这种方式,可以确保在任一子任务失败时,其他子任务能够及时检测到取消信号并停止执行,避免浪费资源。

小结

  • 使用 sync.WaitGroup 时,确保传递的是指针,且在启动 Goroutine 之前调用 wg.Add()
  • 在需要控制子任务的执行,如在某个任务失败时取消其他任务的场景下,结合 context 使用可以更好地管理并发任务。
  • sync.WaitGroup 适用于等待所有子任务完成的简单场景;对于更复杂的并发控制,context 提供了强大的功能。

通过对 sync.WaitGroupcontext 的灵活运用,可以更有效地管理 Go 程序中的并发操作,提高程序的健壮性和效率。

复制全文 生成海报 Go语言 并发编程 编程技巧

推荐文章

10个极其有用的前端库
2024-11-19 09:41:20 +0800 CST
Golang Select 的使用及基本实现
2024-11-18 13:48:21 +0800 CST
浏览器自动播放策略
2024-11-19 08:54:41 +0800 CST
在Vue3中实现代码分割和懒加载
2024-11-17 06:18:00 +0800 CST
PHP 允许跨域的终极解决办法
2024-11-19 08:12:52 +0800 CST
JavaScript设计模式:发布订阅模式
2024-11-18 01:52:39 +0800 CST
最全面的 `history` 命令指南
2024-11-18 21:32:45 +0800 CST
使用xshell上传和下载文件
2024-11-18 12:55:11 +0800 CST
OpenCV 检测与跟踪移动物体
2024-11-18 15:27:01 +0800 CST
软件定制开发流程
2024-11-19 05:52:28 +0800 CST
rangeSlider进度条滑块
2024-11-19 06:49:50 +0800 CST
php机器学习神经网络库
2024-11-19 09:03:47 +0800 CST
Vue3中如何处理组件间的动画?
2024-11-17 04:54:49 +0800 CST
详解 Nginx 的 `sub_filter` 指令
2024-11-19 02:09:49 +0800 CST
PHP 微信红包算法
2024-11-17 22:45:34 +0800 CST
回到上次阅读位置技术实践
2025-04-19 09:47:31 +0800 CST
Python Invoke:强大的自动化任务库
2024-11-18 14:05:40 +0800 CST
nuxt.js服务端渲染框架
2024-11-17 18:20:42 +0800 CST
php腾讯云发送短信
2024-11-18 13:50:11 +0800 CST
liunx宝塔php7.3安装mongodb扩展
2024-11-17 11:56:14 +0800 CST
设置mysql支持emoji表情
2024-11-17 04:59:45 +0800 CST
imap_open绕过exec禁用的脚本
2024-11-17 05:01:58 +0800 CST
Vue3中如何扩展VNode?
2024-11-17 19:33:18 +0800 CST
MySQL数据库的36条军规
2024-11-18 16:46:25 +0800 CST
mysql int bigint 自增索引范围
2024-11-18 07:29:12 +0800 CST
平面设计常用尺寸
2024-11-19 02:20:22 +0800 CST
程序员茄子在线接单