WaitGroup原理分析

背景

在實際業務開發中,我們會遇到以下場景:請求數據庫,批量獲取1000條數據記錄后,處理數據
為了減少因一次批量獲取的數據太多,導致的數據庫延時增加,我們可以把一次請求拆分成多次請求,并發去處理,當所有的并發請求完成后,再繼續處理這些返回的數據
golang中的WaitGroup,就可以幫助我們實現上述的場景

快速入門

背景:開啟10個goroutine并發執行,等待所有goroutine執行完成后,當前goroutine打印執行完成

func TestWaitGroup(t *testing.T) {var wg sync.WaitGroupfor i := 0; i < 10; i++ {index := igo func() {wg.Add(1)defer wg.Done()fmt.Println(fmt.Sprintf("%+v 正在執行", index))}()}wg.Wait()fmt.Println("TestWaitGroup method done")
}

源碼分析

golang版本:1.18.2

源碼路徑:src/sync/waitgroup.go

// A WaitGroup waits for a collection of goroutines to finish.
// The main goroutine calls Add to set the number of
// goroutines to wait for. Then each of the goroutines
// runs and calls Done when finished. At the same time,
// Wait can be used to block until all goroutines have finished.
//
// A WaitGroup must not be copied after first use.
// WaitGroup 等待 goroutine 集合完成
// 主 goroutine 調用 Add 設置等待的 goroutine 數量
// 然后每個 goroutine 運行并在完成時調用 Done
// 同時,Wait 可以用來阻塞,直到所有 goroutine 都完成
type WaitGroup struct {noCopy noCopy// 64-bit value: high 32 bits are counter, low 32 bits are waiter count.// 64-bit atomic operations require 64-bit alignment, but 32-bit// compilers only guarantee that 64-bit fields are 32-bit aligned.// For this reason on 32 bit architectures we need to check in state()// if state1 is aligned or not, and dynamically "swap" the field order if// needed.// 64位值:高32位是計數器,低32位是waiter計數// 64位原子操作需要64位對齊,但32位編譯器僅保證64位字段是32位對齊的// 因此,在 32 位架構上,我們需要在 state() 中檢查 state1 是否對齊,并在需要時動態“交換”字段順序state1 uint64state2 uint32
}

noCopy:WaitGroup在首次使用后,不能被復制
state1,state2:一共占用12字節,保存了三類信息:4字節保存goroutine計數,4字節保存waiter計數,4字節保存信號量
WaitGroup對外提供了以下三個方法:

// 設置等待的goroutine數量
func (wg *WaitGroup) Add(delta int)
// goroutine執行完成
func (wg *WaitGroup) Done()
// 阻塞等待所有的goroutine都執行完成
func (wg *WaitGroup) Wait()

Add

// state returns pointers to the state and sema fields stored within wg.state*.
func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {if unsafe.Alignof(wg.state1) == 8 || uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {// state1 is 64-bit aligned: nothing to do.return &wg.state1, &wg.state2} else {// state1 is 32-bit aligned but not 64-bit aligned: this means that// (&state1)+4 is 64-bit aligned.state := (*[3]uint32)(unsafe.Pointer(&wg.state1))return (*uint64)(unsafe.Pointer(&state[1])), &state[0]}
}// Add adds delta, which may be negative, to the WaitGroup counter.
// If the counter becomes zero, all goroutines blocked on Wait are released.
// If the counter goes negative, Add panics.
//
// Note that calls with a positive delta that occur when the counter is zero
// must happen before a Wait. Calls with a negative delta, or calls with a
// positive delta that start when the counter is greater than zero, may happen
// at any time.
// Typically this means the calls to Add should execute before the statement
// creating the goroutine or other event to be waited for.
// If a WaitGroup is reused to wait for several independent sets of events,
// new Add calls must happen after all previous Wait calls have returned.
// See the WaitGroup example.
// Add 將 delta(可能為負)添加到 WaitGroup 計數器。
// 如果計數器變為零,則所有在 Wait 上阻塞的 goroutine 都會被釋放。
// 如果計數器變為負數,則添加panic。
// 請注意,計數器為零時發生的具有正增量的調用必須在等待之前發生。 
// 具有負增量的調用或在計數器大于零時開始的具有正增量的調用可能隨時發生。
// 通常,這意味著對 Add 的調用應該在創建 goroutine 或其他要等待的事件的語句之前執行。
// 如果重用一個 WaitGroup 來等待幾個獨立的事件集,新的 Add 調用必須在所有先前的 Wait 調用返回后發生。
func (wg *WaitGroup) Add(delta int) {statep, semap := wg.state()if race.Enabled {_ = *statep // trigger nil deref earlyif delta < 0 {// Synchronize decrements with Wait.race.ReleaseMerge(unsafe.Pointer(wg))}race.Disable()defer race.Enable()}// 記錄goroutine計數state := atomic.AddUint64(statep, uint64(delta)<<32)// 獲取goroutine計數v := int32(state >> 32)// 獲取waiter計數w := uint32(state)if race.Enabled && delta > 0 && v == int32(delta) {// The first increment must be synchronized with Wait.// Need to model this as a read, because there can be// several concurrent wg.counter transitions from 0.race.Read(unsafe.Pointer(semap))}// goroutine計數小于0if v < 0 {panic("sync: negative WaitGroup counter")}// w != 0說明已經執行了Wait且還有阻塞等待的goroutine,此時不允許在執行Addif w != 0 && delta > 0 && v == int32(delta) {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}// 存在沒有執行完成的goroutine,或者當前沒有waiter,直接返回if v > 0 || w == 0 {return}// This goroutine has set counter to 0 when waiters > 0.// Now there can't be concurrent mutations of state:// - Adds must not happen concurrently with Wait,// - Wait does not increment waiters if it sees counter == 0.// Still do a cheap sanity check to detect WaitGroup misuse.// 此時goroutine計數為0,且waiter計數大于0,不然上一步就返回了// 現在以下狀態不能同時發生:// 1. 并發調用Add和Wait// 2. 當goroutine計數為0時,Wait不會繼續增加waiter計數// 仍然做一個廉價的健全性檢查來檢測 WaitGroup 的濫用,防止以上情況發生if *statep != state {panic("sync: WaitGroup misuse: Add called concurrently with Wait")}// Reset waiters count to 0.// 重置waiter計數*statep = 0// 喚醒所有的waiterfor ; w != 0; w-- {runtime_Semrelease(semap, false, 0)}
}

delta代表本次需要記錄的goroutine計數,可能為負數
64位原子操作需要64位對齊,但32位編譯器僅保證64位字段是32位對齊的
當state1是64位對齊時,state1高32位是goroutine計數,低32位是waiter計數
當state1不是64位對齊時,動態“交換”字段順序
記錄goroutine計數的變化delta
如果goroutine計數小于0,則直接panic
如果已經執行了Wait且還有阻塞等待的goroutine,此時不允許在執行Add
如果存在沒有執行完成的goroutine,或者當前沒有waiter,直接返回
當goroutine計數為0,且waiter計數大于0時,現在以下狀態不能同時發生:

并發調用Add和Wait
當goroutine計數為0時,Wait不會繼續增加waiter計數

簡單校驗通過后,重置waiter計數為0,喚醒所有阻塞等待的waiter

Done

// Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done() {wg.Add(-1)
}

調用Add,delta = -1,代表goroutine計數-1

Wait

// Wait blocks until the WaitGroup counter is zero.
func (wg *WaitGroup) Wait() {statep, semap := wg.state()if race.Enabled {_ = *statep // trigger nil deref earlyrace.Disable()}for {state := atomic.LoadUint64(statep)// 獲取goroutine計數v := int32(state >> 32)// 獲取waiter計數w := uint32(state)// goroutine計數為0,不需要等待,直接返回if v == 0 {// Counter is 0, no need to wait.if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(wg))}return}// Increment waiters count.// waiter計數+1if atomic.CompareAndSwapUint64(statep, state, state+1) {if race.Enabled && w == 0 {// Wait must be synchronized with the first Add.// Need to model this is as a write to race with the read in Add.// As a consequence, can do the write only for the first waiter,// otherwise concurrent Waits will race with each other.race.Write(unsafe.Pointer(semap))}// 阻塞,等待goroutine計數為0后喚醒繼續執行runtime_Semacquire(semap)// Wait還沒有執行完成,就開始復用WaitGroupif *statep != 0 {panic("sync: WaitGroup is reused before previous Wait has returned")}if race.Enabled {race.Enable()race.Acquire(unsafe.Pointer(wg))}return}}
}

調用state(),保證字段內存對齊
如果goroutine計數為0,不需要等待,直接返回
嘗試對waiter計數+1,若失敗,則繼續下一輪重試
對waiter計數+1成功,則阻塞當前goroutine,等待goroutine計數為0后喚醒繼續執行
喚醒繼續執行后,簡單判斷是否存在Wait還沒有執行完成,就開始復用WaitGroup的情況,如果有,則panic;如果沒有,則直接返回

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/209355.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/209355.shtml
英文地址,請注明出處:http://en.pswp.cn/news/209355.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C#-快速剖析文件和流,并使用

目錄 一、概述 二、文件系統 1、檢查驅動器信息 2、Path 3、文件和文件夾 三、流 1、FileStream 2、StreamWriter與StreamReader 3、BinaryWriter與BinaryReader 一、概述 文件&#xff0c;具有永久存儲及特定順序的字節組成的一個有序、具有名稱的集合&#xff1b; …

大模型的全方位評估

摘要&#xff1a; 評估通過提供一種跟蹤進度、理解模型以及記錄其能力和偏差的方法&#xff0c;為基礎大模型提供了背景。基礎大模型挑戰了機器學習中標準評估范式實現這些目標的能力&#xff0c;因為它們距離特定任務只有一步之遙。為了設想適合基礎模型的評估新范式&#xff…

枚舉 LeetCode2048. 下一個更大的數值平衡數

如果整數 x 滿足&#xff1a;對于每個數位 d &#xff0c;這個數位 恰好 在 x 中出現 d 次。那么整數 x 就是一個 數值平衡數 。 給你一個整數 n &#xff0c;請你返回 嚴格大于 n 的 最小數值平衡數 。 如果n的位數是k&#xff0c;n它的下一個大的平衡數一定不會超過 k1個k1…

圖論——最小生成樹

圖論——最小生成樹 A wise man changes his mind, a fool never will 生成樹 一個連通圖的生成樹是一個極小的連通子圖&#xff0c;它包含圖中全部的n個頂點&#xff0c;但只有構成一棵樹的n-1條邊。 最小生成樹 在這些邊中選擇N-1條出來&#xff0c;連接所有的N個點。這N-1…

Java后端的登錄、注冊接口是怎么實現的

目錄 Java后端的登錄、注冊接口是怎么實現的 Java后端的登錄接口是怎么實現的 Java后端的注冊接口怎么實現&#xff1f; 如何防止SQL注入攻擊&#xff1f; Java后端的登錄、注冊接口是怎么實現的 Java后端的登錄接口是怎么實現的 Java后端的登錄接口的實現方式有很多種&a…

使用git出現的問題

保證 首先保證自己的git已經下載 其次保證自己的gitee賬號已經安裝并且已經生成ssh公鑰 保證自己要push的代碼在要上傳的文件夾內并且配置文件等都在父文件夾&#xff08;也就是文件沒有套著文件&#xff09; 問題 1 $ git push origin master gitgitee.com: Permission de…

近似同態加密的 IND/SIM-CPA+ 安全性:對于 CKKS 實際有效的攻擊

參考文獻&#xff1a; [LM21] Li B, Micciancio D. On the security of homomorphic encryption on approximate numbers[C]//Advances in Cryptology–EUROCRYPT 2021: 40th Annual International Conference on the Theory and Applications of Cryptographic Techniques, Z…

【Linux】命令expect使用詳解

&#x1f984; 個人主頁——&#x1f390;個人主頁 &#x1f390;?&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; 感謝點贊和關注 &#xff0c;每天進步一點點&#xff01;加油&#xff01;&…

【上海大學數字邏輯實驗報告】五、記憶元件測試

一、實驗目的 掌握R-S觸發器、D觸發器和JK觸發器的工作原理及其相互轉換。學會用74LS00芯片構成鐘控RS觸發器。學會用74LS112實現D觸發器學會在Quartus II上用D觸發器實現JK觸發器。 二、實驗原理 基本R-S觸發器是直接復位-置位的觸發器&#xff0c;它是構成各種功能的觸發器…

AI文檔助手,當下熱門的AI文檔助手【2024】

在當今信息爆炸的時代&#xff0c;文檔創作的需求愈發龐大。為了滿足用戶對高效、準確、原創性文檔的需求&#xff0c;人工智能技術的應用日益廣泛。本文將專心分享AI文檔助手領域的熱門推薦。 AI文檔助手的背景與應用 AI文檔助手作為人工智能技術在文檔創作領域的一大應用&am…

nginx配置自建SSL證書

文章目錄 前言配置SSL證書SSL證書放在 Nginx 而不放在應用服務器上的好處Nginx只能轉發http協議嗎Nginx轉發TCP協議會收到端口限制嗎Nginx本身能將Websocket數據轉化成TCP數據嗎總結 前言 之前的一篇文章《自建CA并生成自簽名SSL證書》中講到為什么要自建CA和自簽名SSL證書&am…

velocity-engine-core是什么?Velocity模板引擎的使用

velocity-engine-core是什么&#xff1f;Velocity模板引擎的使用 1. 常見的模板引擎2. Velocity 的語法3.Velocity的使用 相信在日常開發中或多或少都聽過或者使用過模板引擎&#xff0c;比如熟知的freemarker, thymeleaf等。而模板引擎就是為了實現View和Data分離而產生的。 而…

C++封裝、繼承(單繼承)、多態詳細分析。

系列文章目錄 文章目錄 系列文章目錄摘要一、基本概念二、多態的分類三、多態的實現3.1 類型兼容與函數重寫3.2 動態聯編與靜態聯編3.3 虛函數3.4 動態多態的實現過程 總結參考文獻 摘要 多態性特征是 C中最為重要的一個特征&#xff0c;熟練使用多態是學好 C的關鍵&#xff0…

Kotlin關鍵字二——constructor和init

在關鍵字一——var和val中最后提到了構造函數&#xff0c;這里就學習下構造函數相關的關鍵字: constructor和init。 主要構造(primary constructor) kotlin和java一樣&#xff0c;在定義類時就自動生成了無參構造 // 會生成默認的無參構造函數 class Person{ }與java不同的是…

configure腳本的常用參數

下面是一些常用的configure選項參數及其解釋&#xff1a; --prefix<directory>&#xff1a;指定安裝目錄--with-<package>&#xff1a;指定依賴的外部庫或軟件包--enable-<feature>&#xff1a;啟用某個特性--disable-<feature>&#xff1a;禁用某個特…

原創 | 數據的確權、流通、入表與監管研究(一):數據與確權

作者&#xff1a;張建軍&#xff0c;中國電科首席專家&#xff0c;神州網信技術總監 本文約7100字&#xff0c;建議閱讀10分鐘 本文主要介紹數據與數據分類、數據確權規則、數據的所有權與其他權利等方面內容&#xff0c;并進行案例分析。 2022年12月發布的《關于構建數據基礎制…

Linux 和 macOS 的主要區別在哪幾個方面呢?

(??? )&#xff0c;Hello我是祐言QAQ我的博客主頁&#xff1a;C/C語言&#xff0c;數據結構&#xff0c;Linux基礎&#xff0c;ARM開發板&#xff0c;網絡編程等領域UP&#x1f30d;快上&#x1f698;&#xff0c;一起學習&#xff0c;讓我們成為一個強大的攻城獅&#xff0…

uniapp實戰 —— 彈出層 uni-popup (含vue3子組件調父組件的方法)

效果預覽 彈出的內容 src\pages\goods\components\ServicePanel.vue <script setup lang"ts"> // 子組件調父組件的方法 const emit defineEmits<{(event: close): void }>() </script><template><view class"service-panel"…

ALSA Compress-Offload API

概述 從 ALSA API 的早期開始&#xff0c;它就被定義為支持 PCM&#xff0c;或考慮到了 IEC61937 等固定比特率的載荷。參數和返回值以幀計算是常態&#xff0c;這使得擴展已有的 API 以支持壓縮數據流充滿挑戰。 最近這些年&#xff0c;音頻數字信號處理器 (DSP) 常常被集成…

git如何配置多個遠程倉庫,并且進行切換

一、配置多個遠程倉庫并進行切換&#xff0c;請按照以下步驟進行操作&#xff1a; 打開命令行終端&#xff0c;并進入您的 Git 倉庫所在的目錄。添加第一個遠程倉庫&#xff0c;使用以下命令&#xff1a;git remote add origin <第一個遠程倉庫的 URL>這里將遠程倉庫命名…