亲宝软件园·资讯

展开

GO中的条件变量sync.Cond详解

鲲鹏飞九万里 人气:0

GO的条件变量

一、条件变量与互斥锁

二、条件变量与互斥锁的配合使用

条件变量的初始化离不开互斥锁,并且它的方法有点也是基于互斥锁的。

条件变量提供的三个方法:等待通知(wait)、单发通知(signal)、广发通知(broadcast)。

三、条件变量的使用

(1)创建锁和条件

	// mailbox 代表信箱
	// 0 代表信箱是空的,1代表信箱是满的
	var mailbox uint8
	// lock 代表信箱上的锁
	var lock sync.RWMutex
	// sendCond 代表专用于发信的条件变量
	var sendCond = sync.NewCond(&lock)
	// reveCond 代表专用于收信的条件变量
	var reveCond = sync.NewCond(lock.RLocker())

(2)使用

lock.Lock()
for mailbox == 1 {
 sendCond.Wait()
}
mailbox = 1
lock.Unlock()
recvCond.Signal()
lock.RLock()
for mailbox == 0 {
 recvCond.Wait()
}
mailbox = 0
lock.RUnlock()
sendCond.Signal()

完整代码:

package main

import (
	"log"
	"sync"
	"time"
)

func main() {
	// mailbox 代表信箱
	// 0 代表信箱是空的,1代表信箱是满的
	var mailbox uint8
	// lock 代表信箱上的锁
	var lock sync.RWMutex
	// sendCond 代表专用于发信的条件变量
	var sendCond = sync.NewCond(&lock)
	// reveCond 代表专用于收信的条件变量
	var reveCond = sync.NewCond(lock.RLocker())

	// sign 用于传递演示完成的信号
	sign := make(chan struct{}, 2)
	max := 5
	go func(max int) { // 用于发信
		defer func() {
			sign <- struct{}{}
		}()
		for i := 1; i <= max; i++ {
			time.Sleep(time.Millisecond * 5)
			lock.Lock()
			for mailbox == 1 {
				sendCond.Wait()
			}
			log.Printf("sender [%d]: the mailbox is empty.", i)
			mailbox = 1
			log.Printf("sender [%d]: the letter has been sent.", i)
			lock.Unlock()
			reveCond.Signal()
		}
	}(max)
	go func(max int) { // 用于收信
		defer func() {
			sign <- struct{}{}
		}()
		for j := 1; j <= max; j++ {
			time.Sleep(time.Millisecond * 500)
			lock.RLock()
			for mailbox == 0 {
				reveCond.Wait()
			}
			log.Printf("receiver [%d]: the mailbox is full.", j)
			mailbox = 0
			log.Printf("receiver [%d]: the letter has been received.", j)
			lock.RUnlock()
			sendCond.Signal()
		}
	}(max)

	<-sign
	<-sign
}

四、条件变量的Wait方法做了什么

(1)条件变量Wait方法主要做的四件事

条件变量的Wait方法主要做了四件事:

(2)为什么要先要锁定条件变量基于的互斥锁,才能调用它的wait方法

因为条件变量的wait方法在阻塞当前的goroutine之前,会解锁它基于的互斥锁。所以在调用wait方法之前,必须先锁定这个互斥锁,否则在调用这个wait方法时,就会引发一个不可恢复的panic。

如果条件变量的Wait方法不先解锁互斥锁的话,那就会造成两个后果:不是当前的程序因panic而崩溃,就是相关的goroutine全面阻塞。

(3)为什么用for语句来包裹调用的wait方法表达式,用if语句不行吗

if语句只会对共享资源的状态检查一次,而for语句却可以做多次检查,直到这个状态改变为止。

之所以做多次检查,主要是为了保险起见。如果一个goroutine因收到通知而被唤醒,但却发现共享资源的状态,依然不符合它的要求i,那么就应该再次调用条件变量的Wait方法,并继续等待下次通知的到来。

这种情况是很有可能发生的,具体如下面所示:

综上所述,在包裹条件变量的Wait方法的时候,我们总是应该使用for语句。

不要用if语句,因为它不能重复地执行“检查状态 - 等待通知 - 被唤醒”的这个流程。

(4)条件变量的Signal方法和Broadcast方法

条件变量signal方法和Broadcast方法都是用来发送通知的,不同的是,前者的通知只会唤醒一个因此而等待的goroutine,而后者的通知却会唤醒所有为此等待的goroutine。

条件变量的Wait方法总会把当前的 goroutine 添加到通知队列的队尾,而它的Signal方法总会从通知队列的队首开始,查找可被唤醒的 goroutine。所以,因Signal方法的通知,而被唤醒的 goroutine 一般都是最早等待的那一个。

条件变量Signal方法和Broadcast方法放置的位置:

与Wait方法不同,条件变量的Signal方法和Broadcast方法并不需要在互斥锁的保护下执行。恰恰相反,我们最好在解锁条件变量基于的那个互斥锁之后,再去调用它的这两个方法。这更有利于程序的运行效率。

条件变量的通知具有即时性:

如果发送通知的时候没有 goroutine 为此等待,那么该通知就会被直接丢弃。在这之后才开始等待的 goroutine 只可能被后面的通知唤醒。

加载全部内容

相关教程
猜你喜欢
用户评论