转自:https://mp.weixin.qq.com/s/HUqTC6i7aSUBVq0bsqfQXw

sync.WaitGroup() 用来做Goroutine的等待,当使用go启动多个并发程序,通过waitgroup可以等待所有go协程结束后再执行后面的代码逻辑

用途:阻塞协程的执行,直到所有的goroutine执行完成

WaitGroup总共有三个方法:Add(delta int),Done(),Wait()。简单的说一下这三个方法的作用。

Add:添加或者减少等待goroutine的数量

Done:相当于Add(-1)

Wait:执行阻塞,直到所有的WaitGroup数量变成0

WaitGroup主要维护了2个计数器,一个是请求计数器 v,一个是等待计数器 w,二者组成一个64bit的值,请求计数器占高32bit,等待计数器占低32bit。

那么等待计数器拿来干嘛?是因为同一个实例的Wait()方法支持多处调用,每一次Wait()方法执行,等待计数器 w 就会加1,而当请求计数器v为0触发Wait()时,要根据w的数量发送w份的信号量,正确的触发所有的Wait(),这虽然不是很常用的一个特性,但是在一些特殊场合是有用处的(比如多个并发都依赖于WaitGroup的实例的结束信号来进行下一个action),演示代码如下

func main() {
wg := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
}()
}
time.Sleep(2 * time.Second)
for j := 0; j < 3; j++ {
go func(i int) {
// 3个地方调用Wait(),通过等待j计时器,每个 Wait 都会被唤醒
wg.Wait()
fmt.Println("wait done now ", i)
}(j)
}
time.Sleep(10 * time.Second)
return
}
/*
输出如下,数字出现的顺序随机
wait done now 1
wait done now 0
wait done now 2
*/

同时,WaitGroup里还对使用逻辑进行了严格的检查,比如Wait()一旦开始不能再执行 Add() 操作

下面是带注释的源码,去掉了不影响代码逻辑的trace部分:

func (wg *WaitGroup) Add(delta int) {
statep := wg.state()
// 更新statep,statep将在wait和add中通过原子操作一起使用
state := atomic.AddUint64(statep, uint64(delta)<<32)
v := int32(state >> 32)
w := uint32(state)
if v < 0 {
panic("sync: negative WaitGroup counter")
}
if w != 0 && delta > 0 && v == int32(delta) {
// wait不等于0说明已经执行了Wait,此时不容许Add
panic("sync: WaitGroup misuse: Add called concurrently with Wait")
}
// 正常情况,Add会让v增加,Done会让v减少,如果没有全部Done掉,此处v总是会大于0的,直到v为0才往下走
// 而w代表是有多少个goruntine在等待done的信号,wait中通过compareAndSwap对这个w进行加1
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.
// 当v为0(Done掉了所有)或者w不为0(已经开始等待)才会到这里,但是在这个过程中又有一次Add,导致statep变化,panic
if *statep != state {
panic("sync: WaitGroup misuse: Add called concurrently with Wait")
}
// Reset waiters count to 0.
// 将statep清0,在Wait中通过这个值来保护信号量发出后还对这个Waitgroup进行操作
*statep = 0
// 将信号量发出,触发wait结束
for ; w != 0; w-- {
runtime_Semrelease(&wg.sema, false)
}
} // Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done() {
wg.Add(-1)
} // Wait blocks until the WaitGroup counter is zero.
func (wg *WaitGroup) Wait() {
statep := wg.state()
for {
state := atomic.LoadUint64(statep)
v := int32(state >> 32)
w := uint32(state)
if v == 0 {
// Counter is 0, no need to wait.
if race.Enabled {
race.Enable()
race.Acquire(unsafe.Pointer(wg))
}
return
}
// Increment waiters count.
// 如果statep和state相等,则增加等待计数,同时进入if等待信号量
// 此处做CAS,主要是防止多个goroutine里进行Wait()操作,每有一个goroutine进行了wait,等待计数就加1
// 如果这里不相等,说明statep,在 从读出来 到 CAS比较 的这个时间区间内,被别的goroutine改写了,那么不进入if,回去再读一次,这样写避免用锁,更高效些
if 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(&wg.sema))
}
// 等待信号量
runtime_Semacquire(&wg.sema)
// 信号量来了,代表所有Add都已经Done
if *statep != 0 {
// 走到这里,说明在所有Add都已经Done后,触发信号量后,又被执行了Add
panic("sync: WaitGroup is reused before previous Wait has returned")
}
return
}
}
}

  

示例

package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup for i := 0; i < 5; i = i + 1 {
wg.Add(1)
go func(n int) {
defer wg.Done()
//defer wg.Add(-1)
EchoNumber(n)
}(i)
} wg.Wait()
} func EchoNumber(i int) {
time.Sleep(5000)
fmt.Println(i)
}

  执行结果:

4
3
0
1
2

 因为哪个协程先被cpu调度是不确定的,所以每次的执行结果也可能是不同的

 这个程序如果不用WaitGroup,那么将看不见输出结果。因为goroutine还没执行完,主线程已经执行完毕。之前执行脚本,都是使用time.Sleep,用一个估计的时间等到子线程执行完,实在太low了

 注释的defer wg.Add(-1)和defer wg.Done()作用一样

虽然channel也能实现,但是如果涉及不到协程之间的数据同步,这个感觉不错

sync—WaitGroup的更多相关文章

  1. Go并发控制之sync.WaitGroup

    WaitGroup 会将main goroutine阻塞直到所有的goroutine运行结束,从而达到并发控制的目的.使用方法非常简单,真心佩服创造Golang的大师们! type WaitGroup ...

  2. sync.WaitGroup和sync.Once

    sync.WaitGroup,顾名思义,等待一组goroutinue运行完毕.sync.WaitGroup声明后即可使用,它有如下方法: func (wg *WaitGroup) Add(delta ...

  3. golang 的 sync.WaitGroup

    WaitGroup的用途:它能够一直等到所有的goroutine执行完成,并且阻塞主线程的执行,直到所有的goroutine执行完成. 官方对它的说明如下: A WaitGroup waits for ...

  4. 《Go语言实战》笔记之协程同步 sync.WaitGroup

    原文地址(欢迎互换友链): http://www.niu12.com/article/8 sync 包提供同步 goroutine 的功能 <p>文档介绍</p><cod ...

  5. golang-----golang sync.WaitGroup解决goroutine同步

    go提供了sync包和channel来解决协程同步和通讯.新手对channel通道操作起来更容易产生死锁,如果时缓冲的channel还要考虑channel放入和取出数据的速率问题. 从字面就可以理解, ...

  6. golang sync.WaitGroup

    //阻塞,直到WaitGroup中的所以过程完成. import ( "fmt" "sync" ) func wgProcess(wg *sync.WaitGr ...

  7. Golang sync.WaitGroup的用法

    0x01 介绍 经常会看到以下了代码: 12345678910111213 package main import ( "fmt" "time") func m ...

  8. Golang的sync.WaitGroup 实现逻辑和源码解析

    在Golang中,WaitGroup主要用来做go Routine的等待,当启动多个go程序,通过waitgroup可以等待所有go程序结束后再执行后面的代码逻辑,比如: func Main() { ...

  9. golang的sync.WaitGroup使用示例

    下面一段代码 len(m) 不一定会打印为 10,为什么?.如果想要 len(m) 打印为 10,应该怎么修改代码? func main() { const N = 10 m := make(map[ ...

随机推荐

  1. 实践 : Ubuntu 上 Testlink 部署

    1.安装apache sudo apt-get install apache2 2. sudo /etc/init.d/apache2 restart 测试: Http:\localhost or I ...

  2. 用于模型选择的AIC与BIC

    一.AIC(Akaike information Criterion)准则 二.BIC(Bayesian information Criterion)准则 参考文献: [1]AIC与BIC区别

  3. 用xshell 连接docker Linux服务器

    用xshell 连接docker Linux服务器

  4. iOS UIViewController生命周期控制

    具体流程,看下图: init方法在init方法中实例化必要的对象(遵从LazyLoad思想)init方法中初始化ViewController本身 loadView方法当view需要被展示而它却是nil ...

  5. list异常

    可能定义的时候没有初始化把 private List<Msg> msgList = new ArrayList<>(); 为啥我android老是忘了new ArrayList

  6. Unity 2D入门基础教程之僵尸先生

    注:这是根据网上教程完成的. 翻译:http://blog.1vr.cn/?p=1422 原文:http://www.raywenderlich.com/61532/unity-2d-tutorial ...

  7. JS实例4

    根据当前年的前五年后五年的年月日 <select id="nian" onclick="Bian()"></select>年 <s ...

  8. url中是否加斜杠/

    通常来说,不加斜杠的形式(如”example.jsp”)请求的是相对于当前页面路径的资源 http://localhost:8080/webapp/examole:加斜杠的形式(”/example.j ...

  9. xml 的 <![CDATA["URL"]]>

    <![CDATA["URL"]]>:用于 xml 处理特殊字符,比如:& <PolicyURL><![CDATA[ http://ectp.t ...

  10. qt5 移植 交叉编译出现错误

    类似这样的错误,当时没有完整的记下来,undefined reference to `std::__detail::_List_node_base@GLIBCXX_3.4.10 当时是在编译qt5cl ...