Go之sync.WaitGroup

Role: Wait for the coroutine created in the main coroutine to finish execution before ending the main coroutine

Go provides sync.WaitGroup. As the name suggests, WaitGroup is used to wait for a group of operations to complete.

WaitGroup internally implements a counter to record the number of outstanding operations. It provides three methods, and Add () is used to add a count. Done () is used to call at the end of the operation to decrement the count. Wait () is used to wait for the end of all operations, that is, the count becomes 0. This function will wait when the count is not 0, and return immediately when the count is 0.

 

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()才会退出
}

, When Add (n) is executed, the request counter v will be increased by n, when Done () is executed, v will be decremented by 1, it can be thought that when v is 0, it is ended, and Wait () can be triggered to execute, the so-called Triggering Wait () is achieved through semaphores.

So why wait for the counter? Because the Wait () method supports concurrency, each time the Wait () method is executed, the wait counter w will increase by 1, and when waiting for v to 0 to trigger Wait (), it needs to send w semaphores according to the number of w, correct Trigger all Wait ().

At the same time, there is a strict check on the usage logic in WaitGroup, such as Wait () cannot be added () once it starts.

The following is the annotated code, removing the trace part that does not affect the logic of the code: 

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
        }
    }
}

 

Published 127 original articles · Likes 24 · Visits 130,000+

Guess you like

Origin blog.csdn.net/Linzhongyilisha/article/details/105472135