You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

447 lines
30 KiB
HTML

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!DOCTYPE html>
<html lang="zh-cn" dir="ltr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="等待组 - sync.WaitGroup # 源码分析 # type WaitGroup struct { noCopy noCopy // waitgroup是不能够拷贝复制的是通过go vet来检测实现 /* waitgroup使用一个int64来计数高32位用来add计数低32位用来记录waiter数量。 若要原子性更新int64就必须保证该int64对齐系数是8即64位对齐。 对于64位系统直接使用一个int64类型字段就能保证原子性要求但对32位系统就不行了。 所以实现的时候并没有直接一个int64 而是使用[3]int32数组若[0]int32地址恰好是8对齐的那就waitgroup int64 = [0]int32 &#43; [1]int32 否则一定是4对齐的 故[0]int32不用恰好错开了4字节此时[1]int32一定是8对齐的。此时waitgroup int64 = [1]int32 &#43; [2]int32 通过这个技巧恰好满足32位和64位系统下int64都能原子性操作 */ state1 [3]uint32 // waitgroup对齐系数是4 } func (wg *WaitGroup) state() (statep *uint64, semap *uint32) { // 当state1是8对齐的则返回低8字节(statep)用来计数即state1[0]是add计数state1[1]是waiter计数 if uintptr(unsafe.Pointer(&amp;wg.state1))%8 == 0 { return (*uint64)(unsafe.Pointer(&amp;wg.state1)), &amp;wg.state1[2] } else { // 反之则返回高8字节用来计数即state1[1]是add计数state1[2]是waiter计数 return (*uint64)(unsafe.Pointer(&amp;wg.state1[1])), &amp;wg.state1[0] } } // Add方法用来更新add计数器。即将原来计数值加上deltadelta可以为负值 // waitgroup的Done方法本质上就是Add(-1) // Add更新之后的计数器值不能小于0。当计数器值等于0时候会释放信号所有调用Wait方法而阻塞的Goroutine不再阻塞释放的信号量=waiter计数 func (wg *WaitGroup) Add(delta int) { statep, semap := wg.">
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#ffffff">
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#343a40">
<meta name="color-scheme" content="light dark"><meta property="og:title" content="等待组 - sync.WaitGroup" />
<meta property="og:description" content="等待组 - sync.WaitGroup # 源码分析 # type WaitGroup struct { noCopy noCopy // waitgroup是不能够拷贝复制的是通过go vet来检测实现 /* waitgroup使用一个int64来计数高32位用来add计数低32位用来记录waiter数量。 若要原子性更新int64就必须保证该int64对齐系数是8即64位对齐。 对于64位系统直接使用一个int64类型字段就能保证原子性要求但对32位系统就不行了。 所以实现的时候并没有直接一个int64 而是使用[3]int32数组若[0]int32地址恰好是8对齐的那就waitgroup int64 = [0]int32 &#43; [1]int32 否则一定是4对齐的 故[0]int32不用恰好错开了4字节此时[1]int32一定是8对齐的。此时waitgroup int64 = [1]int32 &#43; [2]int32 通过这个技巧恰好满足32位和64位系统下int64都能原子性操作 */ state1 [3]uint32 // waitgroup对齐系数是4 } func (wg *WaitGroup) state() (statep *uint64, semap *uint32) { // 当state1是8对齐的则返回低8字节(statep)用来计数即state1[0]是add计数state1[1]是waiter计数 if uintptr(unsafe.Pointer(&amp;wg.state1))%8 == 0 { return (*uint64)(unsafe.Pointer(&amp;wg.state1)), &amp;wg.state1[2] } else { // 反之则返回高8字节用来计数即state1[1]是add计数state1[2]是waiter计数 return (*uint64)(unsafe.Pointer(&amp;wg.state1[1])), &amp;wg.state1[0] } } // Add方法用来更新add计数器。即将原来计数值加上deltadelta可以为负值 // waitgroup的Done方法本质上就是Add(-1) // Add更新之后的计数器值不能小于0。当计数器值等于0时候会释放信号所有调用Wait方法而阻塞的Goroutine不再阻塞释放的信号量=waiter计数 func (wg *WaitGroup) Add(delta int) { statep, semap := wg." />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://go.cyub.vip/concurrency/sync-waitgroup/" /><meta property="article:section" content="concurrency" />
<title>等待组 - sync.WaitGroup | 深入Go语言之旅</title>
<link rel="manifest" href="/manifest.json">
<link rel="icon" href="/favicon.png" >
<link rel="stylesheet" href="/book.min.f06572240ce28e67eb332ac5cf5d59a696c47ad4c6f700d5842c5ed93dd8ec77.css" integrity="sha256-8GVyJAzijmfrMyrFz11ZppbEetTG9wDVhCxe2T3Y7Hc=" crossorigin="anonymous">
<script defer src="/flexsearch.min.js"></script>
<script defer src="/en.search.min.7e9d53d4a20eea8c87bf76a4502bd21aa041c1ef2adc7e37ffc5339c57accccd.js" integrity="sha256-fp1T1KIO6oyHv3akUCvSGqBBwe8q3H43/8UznFeszM0=" crossorigin="anonymous"></script>
<!--
Made with Book Theme
https://github.com/alex-shpak/hugo-book
-->
</head>
<body dir="ltr">
<input type="checkbox" class="hidden toggle" id="menu-control" />
<input type="checkbox" class="hidden toggle" id="toc-control" />
<main class="container flex">
<aside class="book-menu">
<div class="book-menu-content">
<nav>
<h2 class="book-brand">
<a class="flex align-center" href="/"><img src="https://static.cyub.vip/images/202310/golang-480.png" alt="Logo" /><span>深入Go语言之旅</span>
</a>
</h2>
<div class="book-search">
<input type="text" id="book-search-input" placeholder="Search" aria-label="Search" maxlength="64" data-hotkeys="s/" />
<div class="book-search-spinner hidden"></div>
<ul id="book-search-results"></ul>
</div>
<ul>
<li>
<a href="https://www.cyub.vip/" target="_blank" rel="noopener">
个人博客
</a>
</li>
<li>
<a href="https://github.com/cyub" target="_blank" rel="noopener">
Github主页
</a>
</li>
<li>
<a href="https://www.topgoer.cn/?ref=go.cyub.vip" target="_blank" rel="noopener">
地鼠文档
</a>
</li>
</ul>
<ul>
<li>
<p><strong>准备篇</strong></p>
<ul>
<li>
<a href="/compiler/">编译流程</a></li>
<li>
<a href="/analysis-tools/">分析工具</a>
<ul>
<li>
<a href="/analysis-tools/gdb/">GDB</a></li>
<li>
<a href="/analysis-tools/dlv/">Delve</a></li>
<li>
<a href="/analysis-tools/go-buildin-tools/">Go 内置工具</a></li>
</ul>
</li>
<li>
<a href="/go-assembly/">Go汇编</a></li>
</ul>
</li>
<li>
<p><strong>基础篇</strong></p>
<ul>
<li>
<a href="/type/">数据类型与数据结构</a>
<ul>
<li>
<a href="/type/string/">字符串</a></li>
<li>
<a href="/type/array/">数组</a></li>
<li>
<a href="/type/slice/">切片</a></li>
<li>
<a href="/type/nil/">nil</a></li>
<li>
<a href="/type/empty_struct/">空结构体</a></li>
<li>
<a href="/type/pointer/">指针</a></li>
<li>
<a href="/type/map/">映射</a></li>
</ul>
</li>
<li>
<a href="/function/">函数</a>
<ul>
<li>
<a href="/function/first-class/">一等公民</a></li>
<li>
<a href="/function/call-stack/">函数调用栈</a></li>
<li>
<a href="/function/pass-by-value/">值传递</a></li>
<li>
<a href="/function/closure/">闭包</a></li>
<li>
<a href="/function/method/">方法</a></li>
</ul>
</li>
<li>
<a href="/feature/">语言特性</a>
<ul>
<li>
<a href="/feature/comma-ok/">逗号ok模式</a></li>
<li>
<a href="/feature/for-range/">遍历 - for-range语法</a></li>
<li>
<a href="/feature/defer/">延迟执行 - defer语法</a></li>
<li>
<a href="/feature/select/">通道选择器 - select语法</a></li>
<li>
<a href="/feature/panic-recover/">恐慌与恢复 - panic/recover</a></li>
</ul>
</li>
</ul>
</li>
<li>
<p><strong>运行时篇</strong></p>
<ul>
<li>
<a href="/concurrency/">并发编程</a>
<ul>
<li>
<a href="/concurrency/memory-model/">内存模型</a></li>
<li>
<a href="/concurrency/context/">上下文 - context</a></li>
<li>
<a href="/concurrency/channel/">通道 - channel</a></li>
<li>
<a href="/concurrency/atomic/">原子操作 - atomic</a></li>
<li>
<a href="/concurrency/sync-map/">并发Map - sync.Map</a></li>
<li>
<a href="/concurrency/sync-waitgroup/"class=active>等待组 - sync.WaitGroup</a></li>
<li>
<a href="/concurrency/sync-once/">一次性操作 - sync.Once</a></li>
<li>
<a href="/concurrency/sync-pool/">缓冲池 - sync.Pool</a></li>
<li>
<a href="/concurrency/sync-cond/">条件变量 - sync.Cond</a></li>
<li>
<a href="/concurrency/sync-mutex/">互斥锁 - sync.Mutex</a></li>
<li>
<a href="/concurrency/sync-rwmutex/">读写锁 - sync.RWMutex</a></li>
</ul>
</li>
<li>
<a href="/gmp/">G-M-P调度机制</a>
<ul>
<li>
<a href="/gmp/gmp-model/">调度机制概述</a></li>
<li>
<a href="/gmp/scheduler/">调度器</a></li>
</ul>
</li>
<li>
<a href="/memory/">内存管理</a>
<ul>
<li>
<a href="/memory/allocator/">内存分配器</a></li>
<li>
<a href="/memory/gc/">GC</a></li>
</ul>
</li>
<li>
<a href="/type-system/">类型系统</a>
<ul>
<li>
<a href="/type-system/type/">类型系统</a></li>
<li>
<a href="/type-system/interface/">接口</a></li>
<li>
<a href="/type-system/reflect/">反射</a></li>
</ul>
</li>
</ul>
</li>
</ul>
</nav>
<script>(function(){var e=document.querySelector("aside .book-menu-content");addEventListener("beforeunload",function(){localStorage.setItem("menu.scrollTop",e.scrollTop)}),e.scrollTop=localStorage.getItem("menu.scrollTop")})()</script>
</div>
</aside>
<div class="book-page">
<header class="book-header">
<div class="flex align-center justify-between">
<label for="menu-control">
<img src="/svg/menu.svg" class="book-icon" alt="Menu" />
</label>
<strong>等待组 - sync.WaitGroup</strong>
<label for="toc-control">
</label>
</div>
</header>
<article class="markdown"><h1 id="等待组---syncwaitgroup">
等待组 - sync.WaitGroup
<a class="anchor" href="#%e7%ad%89%e5%be%85%e7%bb%84---syncwaitgroup">#</a>
</h1>
<h2 id="源码分析">
源码分析
<a class="anchor" href="#%e6%ba%90%e7%a0%81%e5%88%86%e6%9e%90">#</a>
</h2>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">type</span> <span style="color:#a6e22e">WaitGroup</span> <span style="color:#66d9ef">struct</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">noCopy</span> <span style="color:#a6e22e">noCopy</span> <span style="color:#75715e">// waitgroup是不能够拷贝复制的是通过go vet来检测实现
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e">/*
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> waitgroup使用一个int64来计数高32位用来add计数低32位用来记录waiter数量。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> 若要原子性更新int64就必须保证该int64对齐系数是8即64位对齐。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> 对于64位系统直接使用一个int64类型字段就能保证原子性要求但对32位系统就不行了。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> 所以实现的时候并没有直接一个int64 而是使用[3]int32数组若[0]int32地址恰好是8对齐的那就waitgroup int64 = [0]int32 + [1]int32
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> 否则一定是4对齐的 故[0]int32不用恰好错开了4字节此时[1]int32一定是8对齐的。此时waitgroup int64 = [1]int32 + [2]int32
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> 通过这个技巧恰好满足32位和64位系统下int64都能原子性操作
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"> */</span>
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">state1</span> [<span style="color:#ae81ff">3</span>]<span style="color:#66d9ef">uint32</span> <span style="color:#75715e">// waitgroup对齐系数是4
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">wg</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">WaitGroup</span>) <span style="color:#a6e22e">state</span>() (<span style="color:#a6e22e">statep</span> <span style="color:#f92672">*</span><span style="color:#66d9ef">uint64</span>, <span style="color:#a6e22e">semap</span> <span style="color:#f92672">*</span><span style="color:#66d9ef">uint32</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 当state1是8对齐的则返回低8字节(statep)用来计数即state1[0]是add计数state1[1]是waiter计数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> uintptr(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">state1</span>))<span style="color:#f92672">%</span><span style="color:#ae81ff">8</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> (<span style="color:#f92672">*</span><span style="color:#66d9ef">uint64</span>)(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">state1</span>)), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">state1</span>[<span style="color:#ae81ff">2</span>]
</span></span><span style="display:flex;"><span> } <span style="color:#66d9ef">else</span> {
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 反之则返回高8字节用来计数即state1[1]是add计数state1[2]是waiter计数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">return</span> (<span style="color:#f92672">*</span><span style="color:#66d9ef">uint64</span>)(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#f92672">&amp;</span><span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">state1</span>[<span style="color:#ae81ff">1</span>])), <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">state1</span>[<span style="color:#ae81ff">0</span>]
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Add方法用来更新add计数器。即将原来计数值加上deltadelta可以为负值
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// waitgroup的Done方法本质上就是Add(-1)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e">// Add更新之后的计数器值不能小于0。当计数器值等于0时候会释放信号所有调用Wait方法而阻塞的Goroutine不再阻塞释放的信号量=waiter计数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">wg</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">WaitGroup</span>) <span style="color:#a6e22e">Add</span>(<span style="color:#a6e22e">delta</span> <span style="color:#66d9ef">int</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">statep</span>, <span style="color:#a6e22e">semap</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">state</span>()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">race</span>.<span style="color:#a6e22e">Enabled</span> { <span style="color:#75715e">// 竞态检查,忽略不看
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">_</span> = <span style="color:#f92672">*</span><span style="color:#a6e22e">statep</span> <span style="color:#75715e">// trigger nil deref early
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">delta</span> &lt; <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// Synchronize decrements with Wait.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">race</span>.<span style="color:#a6e22e">ReleaseMerge</span>(<span style="color:#a6e22e">unsafe</span>.<span style="color:#a6e22e">Pointer</span>(<span style="color:#a6e22e">wg</span>))
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">race</span>.<span style="color:#a6e22e">Disable</span>()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">race</span>.<span style="color:#a6e22e">Enable</span>()
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">state</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">AddUint64</span>(<span style="color:#a6e22e">statep</span>, uint64(<span style="color:#a6e22e">delta</span>)<span style="color:#f92672">&lt;&lt;</span><span style="color:#ae81ff">32</span>) <span style="color:#75715e">// delta左移32位然后原子性更新statep值并返回更新后的statep值
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> int32(<span style="color:#a6e22e">state</span> <span style="color:#f92672">&gt;&gt;</span> <span style="color:#ae81ff">32</span>) <span style="color:#75715e">// state高位的4字节是add计数赋值给v
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">w</span> <span style="color:#f92672">:=</span> uint32(<span style="color:#a6e22e">state</span>) <span style="color:#75715e">// state低位的4字节是waiter计数赋值给w
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">v</span> &lt; <span style="color:#ae81ff">0</span> { <span style="color:#75715e">// add计数不能为负值。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> panic(<span style="color:#e6db74">&#34;sync: negative WaitGroup counter&#34;</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// Add方法与Wait方法不能并发调用
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">w</span> <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">delta</span> &gt; <span style="color:#ae81ff">0</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">v</span> <span style="color:#f92672">==</span> int32(<span style="color:#a6e22e">delta</span>) {
</span></span><span style="display:flex;"><span> panic(<span style="color:#e6db74">&#34;sync: WaitGroup misuse: Add called concurrently with Wait&#34;</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">v</span> &gt; <span style="color:#ae81ff">0</span> <span style="color:#f92672">||</span> <span style="color:#a6e22e">w</span> <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span> { <span style="color:#75715e">// add计数大于0或者waiter计数等于0直接返回不执行后面逻辑。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// statep指向state1字段其指向的值和state进行比较如果不一样说明存在并发调用了Add和Wait方法
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// 此时v = 0, w &gt; 0这个时候waitgroup的add计数和waiter计数不能再更改了。
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// *statep != state情况举例假定当前groutine是g1执行到此处时,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// 恰好另外一个groutine g2并发调用了Wait方法
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// 那么waitgroup的state1字段会更新而g1中w的值还是g2调用Wait方法之前的waiter数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#75715e">// 这会导致总有一个g永远得不到释放信号从而造成g泄漏。所以此处要进行panic判断
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">statep</span> <span style="color:#f92672">!=</span> <span style="color:#a6e22e">state</span> {
</span></span><span style="display:flex;"><span> panic(<span style="color:#e6db74">&#34;sync: WaitGroup misuse: Add called concurrently with Wait&#34;</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span><span style="color:#a6e22e">statep</span> = <span style="color:#ae81ff">0</span> <span style="color:#75715e">// 重置计数器为0
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">for</span> ; <span style="color:#a6e22e">w</span> <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span>; <span style="color:#a6e22e">w</span><span style="color:#f92672">--</span> { <span style="color:#75715e">// 有w个waiter则释放出w个信号
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">runtime_Semrelease</span>(<span style="color:#a6e22e">semap</span>, <span style="color:#66d9ef">false</span>, <span style="color:#ae81ff">0</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Done() == Add(-1)
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">wg</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">WaitGroup</span>) <span style="color:#a6e22e">Done</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">Add</span>(<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// Wait会阻塞当前goroutine直到add计数器值为0
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">wg</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">WaitGroup</span>) <span style="color:#a6e22e">Wait</span>() {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">statep</span>, <span style="color:#a6e22e">semap</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">wg</span>.<span style="color:#a6e22e">state</span>()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> {
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">state</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">LoadUint64</span>(<span style="color:#a6e22e">statep</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">v</span> <span style="color:#f92672">:=</span> int32(<span style="color:#a6e22e">state</span> <span style="color:#f92672">&gt;&gt;</span> <span style="color:#ae81ff">32</span>)
</span></span><span style="display:flex;"><span> <span style="color:#a6e22e">w</span> <span style="color:#f92672">:=</span> uint32(<span style="color:#a6e22e">state</span>)
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 使用for + cas进制原子性更新waiter计数
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">atomic</span>.<span style="color:#a6e22e">CompareAndSwapUint64</span>(<span style="color:#a6e22e">statep</span>, <span style="color:#a6e22e">state</span>, <span style="color:#a6e22e">state</span><span style="color:#f92672">+</span><span style="color:#ae81ff">1</span>) {
</span></span><span style="display:flex;"><span> <span style="color:#75715e">// 更新成功后开始获取信号未获取到信号的话则当前g一直阻塞
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span> <span style="color:#a6e22e">runtime_Semacquire</span>(<span style="color:#a6e22e">semap</span>)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">statep</span> <span style="color:#f92672">!=</span> <span style="color:#ae81ff">0</span> {
</span></span><span style="display:flex;"><span> panic(<span style="color:#e6db74">&#34;sync: WaitGroup is reused before previous Wait has returned&#34;</span>)
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h3 id="总结">
总结
<a class="anchor" href="#%e6%80%bb%e7%bb%93">#</a>
</h3>
<ul>
<li>waitgroup是不能值传递的</li>
<li>Add方法的传值可以是负数但加上该传值之后的waitgroup计数器值不能是负值</li>
<li>Done方法实际上调用的是Add(-1)</li>
<li>Add方法和Wait方法不能并发调用</li>
<li>Wait方法可以多次调用调用此方法的goroutine会阻塞一直阻塞到waitgroup计数器值变为0。</li>
</ul>
</article>
<footer class="book-footer">
<div class="flex flex-wrap justify-between">
</div>
<script>(function(){function e(e){const t=window.getSelection(),n=document.createRange();n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n)}document.querySelectorAll("pre code").forEach(t=>{t.addEventListener("click",function(){if(window.getSelection().toString())return;e(t.parentElement),navigator.clipboard&&navigator.clipboard.writeText(t.parentElement.textContent)})})})()</script>
</footer>
<div class="book-comments">
<div id="disqus_thread"></div>
<script type="application/javascript">
window.disqus_config = function () {
};
(function() {
if (["localhost", "127.0.0.1"].indexOf(window.location.hostname) != -1) {
document.getElementById('disqus_thread').innerHTML = 'Disqus comments not available by default when the website is previewed locally.';
return;
}
var d = document, s = d.createElement('script'); s.async = true;
s.src = '//' + "go-cyub-vip" + '.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="https://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
</div>
<label for="menu-control" class="hidden book-menu-overlay"></label>
</div>
</main>
</body>
</html>