首页 > 其他分享 >一个小例子,给你讲透典型的 Go 并发操作

一个小例子,给你讲透典型的 Go 并发操作

时间:2024-09-14 12:53:07浏览次数:3  
标签:wg WaitGroup sync 并发 任务 例子 Done Go 执行


如果你有一个任务可以分解成多个子任务进行处理,同时每个子任务没有先后执行顺序的限制,等到全部子任务执行完毕后,再进行下一步处理。这时每个子任务的执行可以并发处理,这种情景下适合使用 sync.WaitGroup

虽然 sync.WaitGroup 使用起来比较简单,但是一不留神很有可能踩到坑里。

sync.WaitGroup 正确使用

比如,有一个任务需要执行 3 个子任务,那么可以这样写:

func main() {
 var wg sync.WaitGroup

 wg.Add(3)

 go handlerTask1(&wg)
 go handlerTask2(&wg)
 go handlerTask3(&wg)

 wg.Wait()

 fmt.Println("全部任务执行完毕.")
}

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

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

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

执行输出:

执行任务 3
执行任务 1
执行任务 2
全部任务执行完毕.

sync.WaitGroup 闭坑指南

01

// 正确
go handlerTask1(&wg)

// 错误
go handlerTask1(wg)

执行子任务时,使用的 sync.WaitGroup 一定要是 wg 的引用类型!

02

注意不要将 wg.Add() 放在 go handlerTask1(&wg) 中!

例如:

// 错误
var wg sync.WaitGroup

go handlerTask1(&wg)

wg.Wait()

...

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

注意 wg.Add() 一定要在 wg.Wait() 执行前执行!

小结

注意 wg.Add()wg.Done() 的计数器保持一致!其实 wg.Done() 就是执行的 wg.Add(-1)

其实 sync.WaitGroup 使用场景比较局限,仅适用于等待全部子任务执行完毕后,再进行下一步处理。如果需求是当第一个子任务执行失败时,通知其他子任务停止运行,这时 sync.WaitGroup 是无法满足的,需要使用到上下文(context)。

sync.WaitGroup + Context

在处理并发任务时,若需在任一子任务失败时终止所有其他子任务,以下示例提供了一种实现方法。

package main

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

// handlerTask 处理单个任务
func handlerTask(ctx context.Context, taskId int, wg *sync.WaitGroup, cancel context.CancelFunc) {
 defer wg.Done()

 fmt.Printf("Request %d is processing...\n", taskId)

 // 模拟请求处理,如果 taskId 为1,则模拟失败
 if taskId == 1 {
  fmt.Printf("Request %d failed\n", taskId)
  cancel() // 取消 context,通知其他请求停止
  return
 }

 // 监听 context.Done() 通道
 select {
 case <-ctx.Done():
  fmt.Printf("Request %d is already cancelled\n", taskId)
  return
 default:
  // 继续执行
  time.Sleep(3 * time.Second) // 模拟耗时操作
  fmt.Printf("Request %d succeeded\n", taskId)
 }
}

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

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

 // 等待所有任务完成或被取消
 go func() {
  wg.Wait()
  fmt.Println("All requests are finished or cancelled")
 }()

 // 给一些时间来处理,防止主 goroutine 过早退出
 time.Sleep(5 * time.Second)
}

解释

1.任务 1 开始处理:

  • 输出 Request 1 is processing...。
  • 检查任务 ID,发现是 1,输出 Request 1 failed。
  • 调用 cancel(),取消上下文 ctx。

2.任务 2 和任务 3 开始处理:

  • 输出 Request 2 is processing... 和 Request 3 is processing...。
  • 由于任务 1 已经调用 cancel(),上下文 ctx 已经被取消。
  • 进入 select 语句,检查 ctx.Done() 通道。
  • 发现 ctx.Done() 通道已被关闭,输出 Request 2 is already cancelled 和 Request 3 is already cancelled。

3.等待所有任务完成或被取消:

  • 等待所有任务完成或被取消。
  • 输出 All requests are finished or cancelled。

通过这种方式,可以确保在任务 1 失败时,其他任务能够立即检测到取消信号并停止执行。

标签:wg,WaitGroup,sync,并发,任务,例子,Done,Go,执行
From: https://blog.51cto.com/u_15183360/12015799

相关文章

  • Go runtime 调度器精讲(五):调度策略
    原创文章,欢迎转载,转载请注明出处,谢谢。0.前言在第四讲我们介绍了maingoroutine是如何运行的。其中针对maingoroutine介绍了调度函数schedule是怎么工作的,对于整个调度器的调度策略并没有介绍,这点是不完整的,这一讲会完善调度器的调度策略部分。1.调度时间点runtim......
  • 一个小例子,给你讲透 Go 配置管理,轻松将其融入到项目中
    在软件开发中,配置管理是一个不可或缺的部分。无论是开发环境、测试环境还是生产环境,我们都需要一种方法来存储和读取配置信息。在Golang项目中,Viper是一个非常流行且功能强大的库,用于处理配置文件。下面我会写一些例子,帮助大家快速上手。什么是Viper?不卖关子,直接上GitHub地址......
  • Golang并发编程中匿名函数的应用与性能考量
    在Golang并发编程中,匿名函数是一种常见且非常实用的编程工具。匿名函数是指没有名称的函数,可以在函数内部定义和使用,或者作为参数传递给其他函数。这种方式不仅简化了代码结构,还能增强代码的灵活性,尤其在并发编程中,匿名函数的使用更能展现其优势。不过,如何合理使用匿名函数并在实......
  • 1小时快速了解Go语言(写给打算转职golang的程序员)
    本文章也有对应的视频讲解:1小时快速了解Go语言开发环境搭建Go语言下载地址:https://go.dev/dl,Windows、Mac、Linux都支持,Windows和Mac下载后直接双击安装即可,Linux下载后解压到任意目录都可以,Linux需要手动设置环境变量GOROOT/GOPATH/PATH。IDE使用Vscode即可,下载地址:https:......
  • Golang中命名参数的高级使用技巧与实例分析
    Golang是一门以简洁、高效著称的编程语言,因其强大的并发处理能力和灵活的语法设计,被广泛应用于各种开发场景。在Golang中,参数传递是日常编程中不可或缺的部分,而命名参数的使用则能够提升代码的可读性和维护性。本文将探讨Golang中命名参数的高级使用技巧,并通过实例进行分析,帮助开......
  • GO语言初步详细介绍以及环境变量的配置----保姆级教程
    一:概述Go语言(也称为Golang)是一种由Google公司设计和开发的静态类型、编译型编程语言。自2009年正式对外发布以来,Go语言以其简洁、高效和强大的并发处理能力迅速赢得了开发者的青睐,并在多个领域得到广泛应用。二:具体说明<1>Go语言的详细介绍1.1Go语言的特点简洁、易读和易写:Go语言......
  • Django的IT人才招聘网站管理系统的设计与实现-附源码03763
    摘   要随着信息技术行业的迅速发展,企业对于高素质的IT人才的需求日益增长。为了满足企业招聘需求和提供更好的求职体验,开发一个高效、可靠的招聘网站管理系统变得尤为重要。论文将首先介绍Django框架的特点和优势,包括其灵活性、可扩展性和安全性等方面。然后,我们将详细......
  • 单例模式 Go
    单例模式单例模式的基本概念单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。它是一种常用的模式,可以在多种编程语言中实现。以下是单例模式的一些关键特点和理论概念:关键特性单一实例:确保只创建一个实例。全局访问:提供一个全局访问点......
  • 高并发环境中保持幂等性
    在高并发环境中保持幂等性是一项重要的挑战。幂等性指的是无论操作执行多少次,其效果都是相同的。确保操作的幂等性可以避免重复执行带来的副作用。以下是一些保持幂等性的常用方法:唯一标识符:请求唯一标识:在每次请求中引入唯一标识符(如UUID或者生成的唯一ID),在处理请求时,系......
  • 解决Go程序可执行文件在alpine容器中无法运行
    Go可执行程序在alpine容器中无法运行的问题解决今天遇到一个问题,我把我的go应用编译好之后,在Dockerfile里指定它到容器中启动,但是启动不起来,我通过测试,发现了这个现象:我的程序是在容器里的,但是我要运行时,它缺提示notfound原因notfound不是说找不到这个程序,而是找不到需要的......