1:为什么先要锁定条件变量基于的互斥锁,才能调用它的Wait方法?
2:为什么要用for语句来包裹调用其Wait方法的表达式,用if语句不行吗?
这些问题我在面试的时候也经常问。你需要对这个Wait方法的内部机制有所了解才能回答上来。
条件变量的Wait方法主要做了四件事。
问题一解答:因为条件变量的Wait方法在阻塞当前的 goroutine 之前,会解锁它基于的互斥锁,所以在调用该Wait方法之前,我们必须先锁定那个互斥锁,否则在调用这个Wait方法时,就会引发一个不可恢复的 panic。为什么条件变量的Wait方法要这么做呢?你可以想象一下,如果Wait方法在互斥锁已经锁定的情况下,阻塞了当前的 goroutine,那么又由谁来解锁呢?别的 goroutine 吗?先不说这违背了互斥锁的重要使用原则,即:成对的锁定和解锁,就算别的 goroutine 可以来解锁,那万一解锁重复了怎么办?由此引发的 panic 可是无法恢复的。如果当前的 goroutine 无法解锁,别的 goroutine 也都不来解锁,那么又由谁来进入临界区,并改变共享资源的状态呢?只要共享资源的状态不变,即使当前的 goroutine 因收到通知而被唤醒,也依然会再次执行这个Wait方法,并再次被阻塞。所以说,如果条件变量的Wait方法不先解锁互斥锁的话,那么就只会造成两种后果:不是当前的程序因 panic 而崩溃,就是相关的 goroutine 全面阻塞。
问题二解答:这主要是为了保险起见。如果一个 goroutine 因收到通知而被唤醒,但却发现共享资源的状态,依然不符合它的要求,那么就应该再次调用条件变量的Wait方法,并继续等待下次通知的到来。
来看下signal()代码示列
type User struct { //模拟用户 mail *Mail } type Mail struct { //模拟邮箱 mail bool sendCond *sync.Cond recvCond *sync.Cond lock sync.RWMutex sendName string recvName string } func NewMail() *Mail { m := Mail{} m.sendCond = sync.NewCond(&m.lock) m.recvCond = sync.NewCond(m.lock.RLocker()) return &m } func (m *Mail) SendMail() { m.lock.Lock() time.Sleep(time.Minute) if m.mail{ m.sendCond.Wait() fmt.Println("已经有邮件了等待接收") } m.mail = true fmt.Println("发送了一封邮件给:",m.recvName) m.lock.Unlock() m.recvCond.Signal() //向接收者发送消息 } func (m *Mail) RecvMail() { m.lock.RLock() if !m.mail{ fmt.Println("没有邮件,等待邮箱发送邮件") m.recvCond.Wait() } m.mail = false fmt.Printf("%s收到一封%s发送的邮件\n",m.recvName,m.sendName) m.lock.RUnlock() m.sendCond.Signal() } func main() { mail := NewMail() lock := make(chan struct{},2) use1 := User{} use1.mail = mail use1.mail.sendName = "jacky" use2 := User{} use2.mail = mail use2.mail.recvName = "rose" //send mail go func() { defer func() { lock <- struct{}{} }() for{ use1.mail.SendMail() time.Sleep(time.Second) } }() go func() { defer func() { lock <- struct{}{} }() for{ use2.mail.RecvMail() time.Sleep(time.Second) } }() <-lock <-lock }
我们再看下条件变量的Broadcast()通知方法示列
func main() { // mailbox 代表信箱。 // 0代表信箱是空的,1代表信箱是满的。 var mailbox uint8 // lock 代表信箱上的锁。 var lock sync.Mutex // sendCond 代表专用于发信的条件变量。 sendCond := sync.NewCond(&lock) // recvCond 代表专用于收信的条件变量。 recvCond := sync.NewCond(&lock) // send 代表用于发信的函数。 send := func(id, index int) { lock.Lock() for mailbox == 1 { sendCond.Wait() } log.Printf("发送邮件用户%d:准备发送邮件...",index) mailbox = 1 log.Printf("发送邮件用户%d:发送了一份邮件...",index) lock.Unlock() recvCond.Broadcast() } // recv 代表用于收信的函数。 recv := func(id, index int) { lock.Lock() for mailbox == 0 { recvCond.Wait() } log.Printf("用户%d收到邮件:发现有份邮件待接收...",index) mailbox = 0 log.Printf("用户%d收到邮件:收到一份邮件...",index) lock.Unlock() sendCond.Signal() // 确定只会有一个发信的goroutine。 } // sign 用于传递演示完成的信号。 sign := make(chan struct{}, 3) max := 6 go func(id, max int) { // 用于发信。 defer func() { sign <- struct{}{} }() for i := 1; i <= max; i++ { time.Sleep(time.Millisecond * 500) send(id, i) } }(0, max) go func(id, max int) { // 用于收信。 defer func() { sign <- struct{}{} }() for j := 1; j <= max; j++ { time.Sleep(time.Millisecond * 200) recv(id, j) } }(1, max/2) go func(id, max int) { // 用于收信。 defer func() { sign <- struct{}{} }() for k := 1; k <= max; k++ { time.Sleep(time.Millisecond * 200) recv(id, k) } }(2, max/2) <-sign <-sign <-sign }
原文:https://www.cnblogs.com/jackey2015/p/11748484.html