条件变量-sync.Cond

什么是条件变量

与sync.Mutex不同,sync.Cond的作用是在对应的共享数据的状态发生变化时,通知一个或者所以因为共享数据而被阻塞的goroutine。sync.Cond总是与sync.Mutex(或者sync.RWMutex)组合使用。sync.Mutex为共享数据的访问提供互斥锁支持,而sync.Cond可以在共享数据的状态的变化向相关goroutine发出通知。

sync.Cond api

sync.Cond总共有3个方法,一个NewCond创建函数

func NewCond(l Locker) *Cond  # 创建NewCond 参数需要是一个`Locker` 一般使用 `sync.Mutex`或者`sync.RWMutex`
func (c *Cond) Wait()  #阻塞当前goroutine,通过`Signal`或者`Broadcast`唤醒
func (c *Cond) Signal() #唤醒一个被阻塞的goroutine,如果没有的话会忽略。 
func (c *Cond) Broadcast() #唤醒一个所有阻塞的goroutine。

我们从sync.Cond提过的api可以看到,它有两种模式,单播和广播。

单播

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	cond := sync.NewCond(new(sync.Mutex))
	condition := 0
	// Consumer
	go func() {
		for {
			cond.L.Lock()
			for condition == 0 {
				cond.Wait()
			}
			condition--
			fmt.Printf("Consumer: %d\n", condition)
			cond.Signal() //注意这边会有多次被忽略的情况
			cond.L.Unlock()
		}
	}()
	// Producer
	for {
		time.Sleep(time.Second)
		cond.L.Lock()
		for condition == 3 {
			cond.Wait()
		}
		condition +=3
		fmt.Printf("Producer: %d\n", condition)
		cond.Signal()
		cond.L.Unlock()
	}
}
$ go run main.go
Producer: 3
Consumer: 2
Consumer: 1
Consumer: 0
Producer: 3
Consumer: 2
Consumer: 1
Consumer: 0

多播

package main

import (
	"fmt"
)
var condition = false

func main() {
	m := sync.Mutex{}
	c := sync.NewCond(&m)
	go func() {
		c.L.Lock()
		for condition == false {
			fmt.Println("goroutine1 wait")
			c.Wait()
		}
		fmt.Println("goroutine1 exit")
		c.L.Unlock()
	}()

	go func() {
		c.L.Lock()
		for condition == false {
			fmt.Println("goroutine2 wait")
			c.Wait()
		}
		fmt.Println("goroutine2 exit")
		c.L.Unlock()
	}()
	time.Sleep(2e9)
	c.L.Lock()
	condition = true
	c.Broadcast()
	c.L.Unlock()
	time.Sleep(2e9)
}
$ go run main.go
goroutine1 wait
goroutine2 wait
broadcast
goroutine1 exit
goroutine2 exit

总结

本小节届时将条件变量的使用,介绍了单播和多播的使用方式。