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
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/84002.shtml
英文地址,請注明出處:http://en.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;避免頻繁地變更需求、過度承諾任務量。合理規劃迭代任…