Go之sync.WaitGroup

役割:メインコルーチンを終了する前に、メインコルーチンで作成されたコルーチンの実行が完了するのを待ちます。

Goはsync.WaitGroupを提供します。名前が示すように、WaitGroupは、操作のグループが完了するまで待機するために使用されます。

WaitGroupは、未処理の操作の数を記録するためのカウンターを内部的に実装しています。3つのメソッドがあり、Add()を使用してカウントを追加します。Done()は、操作の最後に呼び出してカウントを減らすために使用されます。Wait()は、すべての操作の終了を待機するために使用されます。つまり、カウントが0になります。この関数は、カウントが0でない場合に待機し、カウントが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()才会退出
}

、Add(n)が実行されると、リクエストカウンターvがn増加し、Done()が実行されると、vが1減少します。vが0の場合、vは終了し、Wait()がトリガーされて実行されます。トリガー待機()は、セマフォによって実現されます。

では、なぜカウンターを待つのでしょうか。Wait()メソッドは同時実行性をサポートしているため、Wait()メソッドが実行されるたびに、待機カウンターwが1ずつ増加し、vが0になるのを待ってWait()をトリガーするときに、wの数に従ってwセマフォを送信する必要があります。すべての待機()をトリガーします。

同時に、WaitGroupには、Wait()を追加すると()を追加すると()を追加できないなど、使用ロジックが厳密にチェックされます。

以下は注釈付きコードで、コードのロジックに影響を与えないトレース部分を削除しています。 

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

 

公開された127元の記事 ウォン称賛24 ビュー130 000 +

おすすめ

転載: blog.csdn.net/Linzhongyilisha/article/details/105472135