Go中的协程并发和并发panic处理

1 协程基础

1.1 协程定义(Goroutine)

  • 概念:Go 语言特有的轻量级线程,由 Go 运行时(runtime)管理,相比系统线程(Thread),创建和销毁成本极低,占用内存小(初始 2KB)。协程是 Go 程序中最基本的并发执行单元。
  • 创建方式:使用go关键字启动一个协程

    func main() {// 匿名函数直接启动协程go func() {fmt.Println("Hello from goroutine!")}() // 调用已定义函数启动协程go func1()go func2()time.Sleep(time.Second) // 等待协程执行,否则主协程退出导致所有协程终止fmt.Println("主协程退出")
    }

1.2 协程调度模型GMP

Go 调度器采用 Goroutine-Machine-Processor (GMP) 模型,核心组件包括:

  • G (Goroutine):协程的抽象,包含执行栈、程序计数器等信息。
  • M (Machine):   对应操作系统线程,实际执行代码的实体。
  • P (Processor):逻辑处理器,持有运行队列(Local Queue)和 G 上下文,必须绑定 M 才能执行 G。

    M 是 “执行任务的实体”,是唯一能运行 Go 代码的载体。G(任务)本身只是一段代码逻辑,必须依赖 M(操作系统线程)才能在 CPU 上执行 
    M和P是绑定关系,必须成对出现

1.2.1 协程创建

  • 当调用 go func() 时,创建一个新的 G 对象,放入当前 P 的 Local Queue。
  • 若 Local Queue 已满(默认 256 个 G),将一半 G 转移到全局队列(Global Queue)。

1.2.2 协程执行

  • M 从绑定的 P 的 Local Queue 获取 G 执行。
  • 若 Local Queue 为空,从 Global Queue 批量获取 G(通常为 P 的 GOMAXPROCS/2)。
  • 若 Global Queue 也为空,从其他 P 的 Local Queue 窃取(Work Stealing) 一半 G。

1.2.3 协程阻塞 / 唤醒

  • 当 G 执行系统调用(如 I/O)时,M 与 P 解绑,P 可被其他 M 接管继续执行队列中的 G。

如果 M 因系统调用被阻塞时,P 继续绑定 M,会导致以下问题:

  1. P 无法工作:P 的本地队列中可能有大量就绪的 G,但由于 M 被阻塞,这些 G 无法执行。
  2. CPU 核心浪费:如果 P 对应一个 CPU 核心,该核心将处于闲置状态,即使还有其他任务可执行。
  3. 因此,当 G 执行系统调用时,调度器会 解绑 M 和 P,允许 P 继续工作,避免 CPU 资源浪费
  • 系统调用返回后,G 重新加入某个 P 的队列等待执行。

2 并发模式 

2.1 共享内存并发

多个协程通过共享变量访问数据,需使用同步原语(如sync.Mutexsync.RWMutex)保护临界区

var (counter intmu      sync.Mutex
)func increment() {mu.Lock()defer mu.Unlock()counter++
}func main() {var wg sync.WaitGroupfor i := 0; i < 1000; i++ {wg.Add(1)go func() {defer wg.Done()increment()}()}wg.Wait()fmt.Println("Counter:", counter) // 输出1000,无竞争
}


2.2 CSP 并发(通过通道通信)

使用channel实现协程间通信和同步,遵循 “不要通过共享内存来通信,而要通过通信来共享内存” 原则

func producer(ch chan<- int) {for i := 0; i < 5; i++ {ch <- i}close(ch)
}func consumer(ch <-chan int) {for num := range ch {fmt.Println("Received:", num)}
}func main() {ch := make(chan int)go producer(ch)consumer(ch)
}


2.3 并发任务控制

// 普通的协程创建方法:
go func() {// your code1
}()
go func() {// your code2
}()
// go on

这段 Go 代码的执行顺序如下:

  1. 启动 goroutine 1:主协程创建并启动第一个匿名函数(// your code1),该函数在后台异步执行。

  2. 启动 goroutine 2:主协程紧接着创建并启动第二个匿名函数(// your code2),同样在后台异步执行。

  3. 主协程继续执行:主协程不会等待这两个 goroutine 完成,而是立即继续执行// go on之后的代码。

  4. 并行执行 goroutine// your code1// your code2的执行顺序取决于调度器,可能并行或交替执行,但它们的完成顺序不确定。由于主协程未等待它们,若主协程提前结束(例如程序退出),这两个 goroutine 可能被强制终止

2.3.1 sync.WaitGroup

wg.Wait()会阻塞直到2个协程执行完后

go func() { 
// func1wg.Done()
}()
go func() {
// func2wg.Done()
}()
wg.Wait()
// go on

这段 Go 代码的执行顺序如下:

  1. 启动 goroutine 1:主协程创建并启动第一个匿名函数(func1),该函数在后台异步执行。

  2. 启动 goroutine 2:主协程紧接着创建并启动第二个匿名函数(func2),同样在后台异步执行。

  3. 主协程阻塞:主协程执行wg.Wait(),进入阻塞状态,等待所有被等待的 goroutine 完成。

  4. 并行执行 goroutinefunc1func2的执行顺序取决于调度器,可能并行或交替执行,但它们的完成顺序不确定。每个 goroutine 在完成任务后调用wg.Done()通知等待组。

  5. 恢复主协程:当所有被等待的 goroutine(即func1func2)都调用了wg.Done()后,wg.Wait()返回,主协程继续执行后续代码(// go on)。

   主协程         |    goroutine 1      |    goroutine 2

---------------------------------------------------------------

wg.Add(2)      |                             |

启动func1       |  开始执行func1   |

启动func2       |                            | 开始执行func2

wg.Wait()阻塞 |          ...               |         ...                

                        |      执行完毕       |

                        | wg.Done()          |

                        |                            |      执行完毕

                        |                            |      wg.Done()

wg.Wait()返回  |                            |

继续执行后续代码

2.3.2 errgroup.Group

var g errgroup.Group
g.Go(func() error {// 任务1:可能返回错误return nil
})
g.Go(func() error {// 任务2:可能返回错误return errors.New("task failed")
})
if err := g.Wait(); err != nil {// 处理首个错误(如任务2失败)
}

执行顺序

  1. 主协程启动两个 goroutine 并行执行

  2. 若其中一个 goroutine 返回非 nil 错误:

    • 自动调用内置的context.CancelFunc

    • 向其他 goroutine 发送取消信号(通过 context)

    • g.Wait()立即返回首个错误

  3. 所有 goroutine(包括未出错的)需主动检查 context 状态并提前退出

2.3.3 对比

特性errgroup.Groupsync.WaitGroup
错误处理自动捕获首个非 nil 错误并终止所有 goroutine不处理错误
执行控制首个错误发生后,其他 goroutine 会被 CancelFunc 终止所有 goroutine 独立运行至完成
结果聚合可返回首个错误,用于统一错误处理无内置错误传递机制
取消机制支持通过 context 传播取消信号无内置取消机制


3. 并发panic处理

协程中发生 panic 若未被捕获,仅会导致该协程崩溃,不会影响其他协程和主程序,但可能导致资源泄漏 

3.1 普通goroutine的panic处理

对于普通的goroutine,可以在协程函数内部使用defer和recover组合来捕获panic。defer语句会将函数推迟到外层函数返回之前执行,而recover函数用于捕获panic,它只能在defer修饰的函数中有效

func worker() {defer func() {if r := recover(); r != nil {fmt.Println("Recovered in worker:", r)}}()// 可能触发panic的代码var data map[string]intdata["key"] = 1 // 触发panic: assignment to entry in nil map
}func main() {go worker()time.Sleep(time.Second)fmt.Println("Main continues")
}

3.2 使用 sync.WaitGroup 时的 panic 处理

sync.WaitGroup常用于等待一组goroutine完成任务。在这种场景下,每个goroutine内部仍需使用defer和recover捕获panic,并且可以通过额外的机制将panic信息传递给主协程。 

import ("fmt""sync"
)type Result struct {Err  errorData interface{}
}func worker(id int, wg *sync.WaitGroup, resultChan chan<- Result) {defer func() {if r := recover(); r != nil {resultChan <- Result{Err: fmt.Errorf("panic in worker %d: %v", id, r)}}}()// 模拟可能触发panic的任务if id == 2 {panic("simulated panic")}resultChan <- Result{Data: fmt.Sprintf("Worker %d finished", id)}wg.Done()
}func main() {var wg sync.WaitGroupresultChan := make(chan Result)numWorkers := 3for i := 1; i <= numWorkers; i++ {wg.Add(1)go worker(i, &wg, resultChan)}go func() {wg.Wait()close(resultChan)}()for result := range resultChan {if result.Err != nil {fmt.Println(result.Err)} else {fmt.Println(result.Data)}}
}

        worker函数通过defer和recover捕获panic,并将错误信息封装成Result结构体发送到resultChan通道。主协程从通道中接收结果,判断是否存在错误并进行相应处理,确保即使有goroutine发生panic,也能及时获取信息并继续执行后续逻辑。 

3.3 使用 errgroup.Group 时的 panic 处理

        errgroup.Group可以方便地并行执行多个任务,并在其中一个任务出错时快速返回错误。然而,它只能处理函数返回的错误,无法自动捕获goroutine内部的panic。因此,需要手动在每个任务函数中添加panic捕获逻辑,并将panic转换为错误返回给errgroup.Group。​

3.3.1 方法一:手动封装panic捕获

import ("fmt""golang.org/x/sync/errgroup"
)func safeGo(g *errgroup.Group, fn func() error) {g.Go(func() error {defer func() {if r := recover(); r != nil {return fmt.Errorf("panic occurred: %v", r)}}()return fn()})
}func main() {var g errgroup.GroupsafeGo(&g, func() error {// 可能触发panic的任务panic("unexpected error")return nil})if err := g.Wait(); err != nil {fmt.Println("Error:", err) // 输出 panic occurred: unexpected error}
}

3.3.2 封装增强版errgroup

import ("fmt""golang.org/x/sync/errgroup""sync"
)type SafeGroup struct {g       errgroup.Groupmu      sync.Mutexpanics  []interface{}
}func (sg *SafeGroup) Go(fn func() error) {sg.g.Go(func() error {defer func() {if r := recover(); r != nil {sg.mu.Lock()sg.panics = append(sg.panics, r)sg.mu.Unlock()}}()return fn()})
}func (sg *SafeGroup) Wait() error {if err := sg.g.Wait(); err != nil {return err}if len(sg.panics) > 0 {return fmt.Errorf("panics occurred: %v", sg.panics)}return nil
}func main() {var sg SafeGroupsg.Go(func() error {panic("panic in goroutine")return nil})if err := sg.Wait(); err != nil {fmt.Println("Error:", err) // 输出: panics occurred: [panic in goroutine]}
}

        这两种方案都能有效地在errgroup.Group中处理panic,方案一通过简单的函数封装,在每个任务中添加panic捕获;方案二则通过自定义结构体,将panic信息集中管理,在Wait方法中统一返回错误,方便在复杂场景下对panic进行更灵活的处理。​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/bicheng/84002.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

性能优化笔记

性能优化转载 https://www.cnblogs.com/tengzijian/p/17858112.html 性能优化的一般策略及方法 简言之&#xff0c;非必要&#xff0c;不优化。先保证良好的设计&#xff0c;编写易于理解和修改的整洁代码。如果现有的代码很糟糕&#xff0c;先清理重构&#xff0c;然后再考…

frida简介及环境搭建

frida简介及环境搭建 一、frida简介二、frida环境搭建一、frida简介 frida是一款轻量级的Hook框架,也可以说是一种动态插桩工具,可以插入一些原生代码到原生app的内存空间去,动态地监视和修改器行为,这些原生平台可以是Win、Mac、Linux、Android或者iOS。 frida分为两个部…

Python实例题:Python计算微积分

目录 Python实例题 题目 代码实现 实现原理 符号计算&#xff1a; 数值计算&#xff1a; 可视化功能&#xff1a; 关键代码解析 1. 导数计算 2. 积分计算 3. 微分方程求解 4. 函数图像绘制 使用说明 安装依赖&#xff1a; 基本用法&#xff1a; 示例输出&#…

Mybatis 拦截器 与 PageHelper 源码解析

Mybatis 拦截器 与 PageHelper 源码解析 一、MyBatis插件机制的设计思想二、Interceptor接口核心解析2.1 核心方法2.2 Intercepts、Signature 注解2.3 自定义拦截器 三、PageHelper 介绍3.1 使用姿势3.2 参数与返回值3.3 使用小细节 四、PageHelper 核心源码解析4.1 分页入口&a…

Linux中 SONAME 的作用

🧠 一、从 -lexample 到 SONAME ✅ 假设你有以下文件结构: /libexample.so → libexample.so.1 /libexample.so.1 → libexample.so.1.0.0 /libexample.so.1.0.0 # SONAME: libexample.so.1/libexample.so.2 → libexample.so.2.0.0 /libexample.so.2.0…

热门消息中间件汇总

文章目录 前言RabbitMQ基本介绍核心特性适用场景 Kafka基本介绍核心特性适用场景 RocketMQ基本介绍核心特性适用场景 NATS基本介绍核心特性适用场景 总结选型建议与未来趋势选型建议未来趋势 结语 前言 大家后&#xff0c;我是沛哥儿。作为技术领域的老湿机&#xff0c;在消息…

【DAY42】Grad-CAM与Hook函数

内容来自浙大疏锦行python打卡训练营 浙大疏锦行 知识点: 回调函数lambda函数hook函数的模块钩子和张量钩子Grad-CAM的示例 作业&#xff1a;理解下今天的代码即可 在深度学习中&#xff0c;我们经常需要查看或修改模型中间层的输出或梯度。然而&#xff0c;标准的前向传播和反…

C++032(static变量)

static变量 static变量是静态存储变量&#xff0c;定义变量时系统就会为其分配固定的存储单元&#xff0c;直至整个程序运行结束。之前我们接触过的全局变量即为static变量&#xff0c;它们存放在静态存储区中。使用static关键字&#xff0c;可将变量声明成static变量。例如&a…

N元语言模型 —— 一文讲懂!!!

目录 引言 一. 基本知识 二.参数估计 三.数据平滑 一.加1法 二.减值法/折扣法 ​编辑 1.Good-Turing 估计 ​编辑 2.Back-off (后备/后退)方法 3.绝对减值法 ​编辑4.线性减值法 5.比较 三.删除插值法(Deleted interpolation) 四.模型自适应 引言 本章节讲的…

SpringAI Alibaba实战文生图

1️⃣ 前置准备&#xff1a;搭建开发环境与服务配置&#x1f680; &#x1f527; 1.1 环境要求 JDK 17&#xff08;推荐 JDK 21&#xff09;、Spring Boot 3.x&#xff08;本案例使用 3.3.4&#xff09;、阿里云百炼大模型服务 API Key。需在阿里云控制台完成服务开通并获取有…

实战二:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…

Kotlin List 操作全面指南

在传统 Java 开发 List 相关的 API 中&#xff0c;有着样板代码冗长、缺乏链式调用、空安全等问题。 Kotlin 这门语言 为 List 提供了丰富的扩展函数&#xff0c;这些函数大大简化了集合操作&#xff0c;解决了传统 Java 集合 API 中的许多痛点。 一、基础操作 1. 创建 List …

硬盘寻址全解析:从 CHS 三维迷宫到 LBA 线性王国

在数字存储的底层世界&#xff0c;硬盘如同一个巨大的 “数据图书馆”&#xff0c;而寻址模式就是决定如何高效找到 “书籍”&#xff08;扇区&#xff09;的核心规则。从早期基于物理结构的 CHS&#xff08;柱面 - 磁头 - 扇区&#xff09;三维寻址&#xff0c;到现代抽象化的…

oracle 11g ADG备库报错ORA-00449 lgwr unexpectedly分析处理

问题背景 昨天遇到群友提问&#xff0c;遇到ADG备库挂了的情况 数据版本:11.2.0.4 操作系统:Centos7.9 环境&#xff1a;ADG主备库&#xff0c;主库为RAC&#xff0c;备库也是RAC 具体报错ORA-00449以及ORA-04021 看样子是LGWR挂了&#xff0c;还有个锁等待。 问题分析 先…

Python——day46通道注意力(SE注意力)

一、 什么是注意力 注意力机制是一种让模型学会「选择性关注重要信息」的特征提取器&#xff0c;就像人类视觉会自动忽略背景&#xff0c;聚焦于图片中的主体&#xff08;如猫、汽车&#xff09;。 transformer中的叫做自注意力机制&#xff0c;他是一种自己学习自己的机制&…

入门AJAX——XMLHttpRequest(Post)

一、前言 在上篇文章中&#xff0c;我们已经介绍了 HMLHttpRequest 的GET 请求的基本用法&#xff0c;并基于我提供的接口练习了两个简单的例子。如果你还没有看过第一篇文章&#xff0c;强烈建议你在学习完上篇文章后再学习本篇文章&#xff1a; &#x1f517;入门AJAX——XM…

​BEV和OCC学习-3:mmdet3d 坐标系

目录 坐标系 转向角 (yaw) 的定义 框尺寸的定义 与支持的数据集的原始坐标系的关系 KITTI Waymo NuScenes Lyft ScanNet SUN RGB-D S3DIS 坐标系 坐标系 — MMDetection3D 1.4.0 文档https://mmdetection3d.readthedocs.io/zh-cn/latest/user_guides/coord_sys_tuto…

Redis高可用架构

概述 Redis作为常用的缓存中间件&#xff0c;因其高性能&#xff0c;丰富的数据结构&#xff0c;使用简单等&#xff0c;常被用在需要一定高性能的To C业务场景中&#xff0c;如「秒杀场景」「用户信息中心」「帖子」「群聊」等等大家常见的业务场景中&#xff0c;以提高服务的…

使用WPF的Microsoft.Xaml.Behaviors.Wpf中通用 UI 元素事件

Nuget下载之后记得要先引用下面的 xmlns:i"http://schemas.microsoft.com/xaml/behaviors" <!-- 鼠标事件 --> <i:EventTrigger EventName"MouseEnter"/> <!-- 鼠标进入 --> <i:EventTrigger EventName"MouseLeave"/&g…

敏捷开发中如何避免过度加班

在敏捷开发过程中避免过度加班&#xff0c;需要明确敏捷原则、合理规划迭代任务、加强团队沟通、优化流程效率、设定合理的工作负荷、注重团队士气和成员健康。明确敏捷原则&#xff0c;即保证可持续发展的步调&#xff0c;避免频繁地变更需求、过度承诺任务量。合理规划迭代任…