sync.WaitGroup
开箱即用,并发安全。使用了原子操作。 拥有三个指针方法:Add、Done、Wait。 WaitGroup的计数器值不能小于0,否则会引发panic。 如果Wait方法的调用和首次Add方法的调用时同时发起的
,比如,在两个同时启动的goroutine中,分别调用这两个方法,就有可能会引发panic。如果Wait方法执行期间,跨越了两个计数周期,就会引发panic
,比如,当前goroutine调用Wait方法,阻塞了。另外一个goroutine调用了Done方法,计数器值变为0;此时会唤醒当前goroutine,并且试图继续执行Wait方法中其余代码。这时,又有一个goroutine调用了Add方法,此时Wait方法就会抛出panic sync: WaitGroup is reused before previous Wait has returned
。使用原则:不要把增加其计数器值的操作和调用其Wait方法的代码,放在不同的 goroutine 中执行。换句话说,要杜绝对同一个WaitGroup值的两种操作的并发执行。
最好统一Add,并发Done,最后Wait
func coordinateWithWaitGroup ( ) {
total := 12
stride := 3
var num int32
fmt. Printf ( "The number: %d [with sync.WaitGroup]\n" , num)
var wg sync. WaitGroup
for i := 1 ; i <= total; i = i + stride {
wg. Add ( stride)
for j := 0 ; j < stride; j++ {
go addNum ( & num, i+ j, wg. Done)
}
wg. Wait ( )
}
fmt. Println ( "End." )
}
sync.Once
开箱即用,并发安全。使用了互斥锁和原子操作。 Do方法接收一个无参数无结果的函数,先检查是否做过(done是不是1,做过就返回),没做过加上锁,再检查是否做过(done是不是0,没做就做)。这两次条件判断被称为跨临界区的双重检查
type Once struct {
m Mutex
done uint32
}
func ( o * Once) Do ( f func ( ) ) {
if atomic. LoadUint32 ( & o. done) == 1 {
return
}
o. m. Lock ( )
defer o. m. Unlock ( )
if o. done == 0 {
defer atomic. StoreUint32 ( & o. done, 1 )
f ( )
}
}
如果参数函数执行很长时间或者根本不会结束,则可能导致相关goroutine阻塞。(因为不结束所以没解锁,所以其他的都抢不到锁) 无论参数函数执行会以什么方式结束,done都会变为1,即便panic了,我们也没办法用同一个Once值重新执行了。