博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Golang的sync.WaitGroup 实现逻辑和源码解析
阅读量:7059 次
发布时间:2019-06-28

本文共 3676 字,大约阅读时间需要 12 分钟。

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

func Main() {    wg := sync.WaitGroup{}    for i := 0; i < 10; i++ {        wg.Add(1)        go func() {            defer wg.Done()            time.Sleep(10 * time.Second)        }()    }    wg.Wait() // 等待在此,等所有go func里都执行了Done()才会退出}

WaitGroup主要是三个方法,Add(int),Done()和Wait(), 其中Done()是调用了Add(-1),推荐使用方法是,先统一Add,在goroutine里并发的Done,然后Wait

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

简单来说,当Add(n)执行时,请求计数器 v 就会加n,当Done()执行时,v 就会减1,可以想到,v 为0时就是结束,可以触发Wait()执行了,所谓的触发Wait()是通过信号量实现的。

那么等待计数器拿来干嘛?是因为Wait()方法支持并发,每一次Wait()方法执行,等待计数器 w 就会加1,而等待v为0触发Wait()时,要根据w的数量发送w份的信号量,正确的触发所有的Wait()。

同时,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        }    }}

 

转载于:https://www.cnblogs.com/jiangz222/p/10348763.html

你可能感兴趣的文章
安全 加密解密 在线工具
查看>>
NSString总结
查看>>
架构师看看
查看>>
架构师速成6.18-初中书单资料推荐 分类: 架构师速成 ...
查看>>
Effective C++ Item 28 避免返回对象内部数据的引用或指针
查看>>
VS2012 ASP.NET 母版页的创建与使用
查看>>
beyond compare 与git diff整合
查看>>
收集的几篇关于Asp.Net处理原理和URL重写的几篇文章
查看>>
Bootstrap Table总结
查看>>
物联网如何跳出“看起来很美”?
查看>>
linux命令行后台运行与调回
查看>>
TryEnterCriticalSection
查看>>
用 Java 实现断点续传参考 (HTTP)
查看>>
VB6.0 取 毫秒级 时间戳
查看>>
unity KeyCode各键值说明
查看>>
Delphi中编写无输出函数名的DLL文件
查看>>
centos的基本命令04
查看>>
Codeforces Round #313 (Div. 2) D. Equivalent Strings(字符串+递归)
查看>>
20个案例掌握PL/SQL 基础
查看>>
windows下查看端口占用以及进程名称
查看>>