1
0
mirror of https://github.com/golang/go synced 2024-11-25 12:57:58 -07:00

sync: Proposal for barrier implementation

As discussed in the mailing list, this adds a simple barrier
implementation to the sync package which enables one or more
goroutines to wait for a counter to go down to zero.

R=rsc, rog, r
CC=golang-dev
https://golang.org/cl/3770045
This commit is contained in:
Gustavo Niemeyer 2011-02-03 12:39:11 -08:00 committed by Rob Pike
parent 838b5ad9d6
commit 63457d089e
4 changed files with 151 additions and 4 deletions

View File

@ -9,6 +9,7 @@ GOFILES=\
mutex.go\ mutex.go\
once.go \ once.go \
rwmutex.go\ rwmutex.go\
waitgroup.go\
# 386-specific object files # 386-specific object files
OFILES_386=\ OFILES_386=\

View File

@ -3,10 +3,10 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// The sync package provides basic synchronization primitives // The sync package provides basic synchronization primitives
// such as mutual exclusion locks. Other than the Once type, // such as mutual exclusion locks. Other than the Once and
// most are intended for use by low-level library routines. // WaitGroup types, most are intended for use by low-level
// Higher-level synchronization is better done via channels // library routines. Higher-level synchronization is better
// and communication. // done via channels and communication.
package sync package sync
import "runtime" import "runtime"

86
src/pkg/sync/waitgroup.go Normal file
View File

@ -0,0 +1,86 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sync
import "runtime"
// A WaitGroup waits for a collection of goroutines to finish.
// The main goroutine calls Add to set the number of
// goroutines to wait for. Then each of the goroutines
// runs and calls Done when finished. At the same time,
// Wait can be used to block until all goroutines have finished.
//
// For example:
//
// for i := 0; i < n; i++ {
// if !condition(i) {
// continue
// }
// wg.Add(1)
// go func() {
// // Do something.
// wg.Done()
// }
// }
// wg.Wait()
//
type WaitGroup struct {
m Mutex
counter int
waiters int
sema *uint32
}
// WaitGroup creates a new semaphore each time the old semaphore
// is released. This is to avoid the following race:
//
// G1: Add(1)
// G1: go G2()
// G1: Wait() // Context switch after Unlock() and before Semacquire().
// G2: Done() // Release semaphore: sema == 1, waiters == 0. G1 doesn't run yet.
// G3: Wait() // Finds counter == 0, waiters == 0, doesn't block.
// G3: Add(1) // Makes counter == 1, waiters == 0.
// G3: go G4()
// G3: Wait() // G1 still hasn't run, G3 finds sema == 1, unblocked! Bug.
// Add adds delta, which may be negative, to the WaitGroup counter.
// If the counter becomes zero, all goroutines blocked on Wait() are released.
func (wg *WaitGroup) Add(delta int) {
wg.m.Lock()
if delta < -wg.counter {
wg.m.Unlock()
panic("sync: negative WaitGroup count")
}
wg.counter += delta
if wg.counter == 0 && wg.waiters > 0 {
for i := 0; i < wg.waiters; i++ {
runtime.Semrelease(wg.sema)
}
wg.waiters = 0
wg.sema = nil
}
wg.m.Unlock()
}
// Done decrements the WaitGroup counter.
func (wg *WaitGroup) Done() {
wg.Add(-1)
}
// Wait blocks until the WaitGroup counter is zero.
func (wg *WaitGroup) Wait() {
wg.m.Lock()
if wg.counter == 0 {
wg.m.Unlock()
return
}
wg.waiters++
if wg.sema == nil {
wg.sema = new(uint32)
}
s := wg.sema
wg.m.Unlock()
runtime.Semacquire(s)
}

View File

@ -0,0 +1,60 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sync_test
import (
. "sync"
"testing"
)
func testWaitGroup(t *testing.T, wg1 *WaitGroup, wg2 *WaitGroup) {
n := 16
wg1.Add(n)
wg2.Add(n)
exited := make(chan bool, n)
for i := 0; i != n; i++ {
go func(i int) {
wg1.Done()
wg2.Wait()
exited <- true
}(i)
}
wg1.Wait()
for i := 0; i != n; i++ {
select {
case <-exited:
t.Fatal("WaitGroup released group too soon")
default:
}
wg2.Done()
}
for i := 0; i != n; i++ {
<-exited // Will block if barrier fails to unlock someone.
}
}
func TestWaitGroup(t *testing.T) {
wg1 := &WaitGroup{}
wg2 := &WaitGroup{}
// Run the same test a few times to ensure barrier is in a proper state.
for i := 0; i != 8; i++ {
testWaitGroup(t, wg1, wg2)
}
}
func TestWaitGroupMisuse(t *testing.T) {
defer func() {
err := recover()
if err != "sync: negative WaitGroup count" {
t.Fatalf("Unexpected panic: %#v", err)
}
}()
wg := &WaitGroup{}
wg.Add(1)
wg.Done()
wg.Done()
t.Fatal("Should panic")
}