版本
- go version 1.10.1
使用方法
//創(chuàng)建Cond
cond := sync.NewCond(new(sync.Mutex))
//等待喚醒
cond.L.Lock()
cond.Wait()
//喚醒一個
cond.Signal()
//喚醒所有
cond.Broadcast()
數據結構
/*
package: sync
file: cond.go
line: 21
*/
type Cond struct {
noCopy noCopy
L Locker
notify notifyList
checker copyChecker
}
-
noCopy:noCopy對象全蝶,擁有一個Lock方法菜循,使得Cond對象在進行go vet掃描的時候体谒,能夠被檢測到是否被復制优烧。
/* package: sync file: cond.go line: 94 */ type noCopy struct{} func (*noCopy) Lock() {}
-
L:實現了Locker接口的鎖對象却妨,通常使用Mutex或RWMutex青责。
/* package: sync file: mutex.go line: 31 */ type Locker interface { Lock() Unlock() }
-
notify:notifyList對象扣囊,維護等待喚醒的goroutine隊列,使用鏈表實現饼灿。
/* package: sync file: runtime.go line: 29 */ type notifyList struct { wait uint32 notify uint32 lock uintptr head unsafe.Pointer tail unsafe.Pointer }
-
checker:copyChecker對象夷家,實際上是uintptr對象蒸其,保存自身對象地址。
/* package: sync file: cond.go line: 79 */ type copyChecker uintptr func (c *copyChecker) check() { if uintptr(*c) != uintptr(unsafe.Pointer(c)) && !atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) && uintptr(*c) != uintptr(unsafe.Pointer(c)) { panic("sync.Cond is copied") } }
- 檢查當前checker的地址是否等于保存在checker中的地址
- 對checker進行原子CAS操作库快,將checker當前地址賦值給為空的checker
- 重復操作1摸袁,防止在進行1和2的時候,有其他gorountine并發(fā)的修改了checker值
- 若1义屏、2靠汁、3都不滿足,則表示當前cond是復制的闽铐,拋出panic
check方法在第一次調用的時候蝶怔,會將checker對象地址賦值給checker,也就是將自身內存地址賦值給自身兄墅。
再次調用checker方法的時候踢星,會將當前checker地址的值與保存的checker地址值進行比較,若不相同則表示當前checker的地址不是第一次調用check方法時候的地址隙咸,即cond對象被復制了沐悦,checker被重新分配了內存地址。
方法
NwoCond
/*
package: sync
file: cond.go
line: 32
*/
func NewCond(l Locker) *Cond {
return &Cond{L: l}
}
NewCond方法傳入一個實現了Locker接口的對象五督,返回一個新的Cond對象指針藏否,保證在多goroutine使用cond的時候,持有的是同一個實例充包。
Wait
/*
package: sync
file: cond.go
line: 52
*/
func (c *Cond) Wait() {
c.checker.check() //step 1
t := runtime_notifyListAdd(&c.notify) //step 2
c.L.Unlock() //step 3
runtime_notifyListWait(&c.notify, t) //step 4
c.L.Lock() //step 5
}
- check檢查副签,保證cond在第一次使用后沒有被復制
- 將notify隊列的等待數加1,并將之前的等待數返回
/* package: runtime file: sema.go line: 476 */ func notifyListAdd(l *notifyList) uint32 { return atomic.Xadd(&l.wait, 1) - 1 }
- 釋放鎖,因此在調用Wait方法前,必須保證獲取到了cond的鎖继薛,否則會報錯
- 將當前goroutine掛起,等待喚醒信號
a. 鎖定notify隊列/* package: runtime file: sema.go line: 485 */ func notifyListWait(l *notifyList, t uint32) { lock(&l.lock) //step a if less(t, l.notify) { //step b unlock(&l.lock) return } s := acquireSudog() //step c s.g = getg() s.ticket = t s.releasetime = 0 t0 := int64(0) if blockprofilerate > 0 { t0 = cputicks() s.releasetime = -1 } if l.tail == nil { //step d l.head = s } else { l.tail.next = s } l.tail = s goparkunlock(&l.lock, "semacquire", traceEvGoBlockCond, 3) //step e if t0 != 0 { blockevent(s.releasetime-t0, 2) } releaseSudog(s) }
b. 如果notif隊列的等待數小于喚醒數 表示當前goroutine不需要再進行等待愈捅,則解鎖notify隊列遏考,直接返回
c. 獲取當前goroutine,設置相關參數蓝谨,將當前等待數賦值給ticket
d. 將當前goroutine加入到notify隊列中
e. 將當前goroutine掛起灌具,等待喚醒信號
- gorountine被喚醒,重新獲取鎖
Signal
/*
package: sync
file: cond.go
line: 64
*/
func (c *Cond) Signal() {
c.checker.check() //step 1
runtime_notifyListNotifyOne(&c.notify) //step 2
}
check檢查譬巫,保證cond在第一次使用后沒有被復制
-
順序喚醒一個等待的gorountine
/* package: runtime file: sema.go line: 485 */ func notifyListNotifyOne(l *notifyList) { if atomic.Load(&l.wait) == atomic.Load(&l.notify) { //step a return } lock(&l.lock) //step b t := l.notify if t == atomic.Load(&l.wait) { unlock(&l.lock) return } atomic.Store(&l.notify, t+1) for p, s := (*sudog)(nil), l.head; s != nil; p, s = s, s.next { //step c if s.ticket == t { n := s.next if p != nil { p.next = n } else { l.head = n } if n == nil { l.tail = p } unlock(&l.lock) s.next = nil readyWithTime(s, 4) return } } unlock(&l.lock) }
a. 如果notify隊列的等待數等于喚醒數咖楣,表示沒有新的goroutine在等待隊列上,不需要喚醒任何goroutine
b. 鎖定notify隊列芦昔,對隊列進行雙檢查诱贿,查看是否有新的goroutine需要被喚醒,若無則將喚醒數加1
c. 遍歷等待喚醒的goroutine隊列咕缎,喚醒ticket等于喚醒數的goroutine珠十,即順序喚醒一個最先加入等待隊列的goroutine
Broadcast
/*
package: sync
file: cond.go
line: 73
*/
func (c *Cond) Broadcast() {
c.checker.check()
runtime_notifyListNotifyAll(&c.notify)
}
check檢查,保證cond在第一次使用后沒有被復制
-
喚醒所有gorountine
/* package: runtime file: sema.go line: 485 */ func notifyListNotifyAll(l *notifyList) { if atomic.Load(&l.wait) == atomic.Load(&l.notify) { //step a return } lock(&l.lock) //step b s := l.head l.head = nil l.tail = nil atomic.Store(&l.notify, atomic.Load(&l.wait)) unlock(&l.lock) for s != nil { //step c next := s.next s.next = nil readyWithTime(s, 4) s = next } }
a. 如果notify隊列的等待數等于喚醒數凭豪,表示沒有新的goroutine在等待隊列上焙蹭,不需要喚醒任何goroutine
b. 鎖定notify隊列,清空等待喚醒隊列嫂伞,將等待數賦值給喚醒數
c. 遍歷等待喚醒的gorountine隊列孔厉,將所有goroutine喚醒
總結
Cond不能被復制:Cond在內部持有一個等待隊列,這個隊列維護所有等待在這個Cond的goroutine帖努。因此若這個Cond允許值傳遞撰豺,則這個隊列在值傳遞的過程中會進行復制,導致在喚醒goroutine的時候出現錯誤拼余。
順序喚醒: notifyList對象持有兩個無限自增的字段wait和notify郑趁,wait字段在有新的goroutine等待的時候加1,notify字段在有新的喚醒信號的時候加1姿搜。在有新的goroutine加入隊列的時候寡润,會將當前wait賦值給goroutine的ticket,喚醒的時候會喚醒ticket等于notify的gourine舅柜。另外梭纹,當wait==notify時表示沒有goroutine需要被喚醒,wait>notify時表示有goroutine需要被喚醒致份,waity恒大于等于notify变抽。