Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > go 条件变量sync.Cond

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

作者:鲲鹏飞九万里

条件变量是基于互斥锁的,它必须基于互斥锁才能发挥作用,条件变量的初始化离不开互斥锁,并且它的方法有点也是基于互斥锁的,这篇文章主要介绍了GO的条件变量sync.Cond,需要的朋友可以参考下

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 只可能被后面的通知唤醒。

到此这篇关于GO的条件变量sync.Cond的文章就介绍到这了,更多相关go 条件变量sync.Cond内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文