并发同步(三)—— 条件变量

一 条件变量

sync.Cond类型即是Go中的条件变量,该类型内部包含一个锁接口。条件变量通常与锁配合使用:

创建条件变量的函数:

func NewCond(l locker) *Cond        // 条件变量必须传入一个锁,二者需要配合使用

*sync.Cond类型有三个方法:

  • Wait: 该方法会阻塞等待条件变量满足条件。也会对锁进行解锁,一旦收到通知则唤醒,并立即锁定该锁
  • Signal: 发送通知(单发),给一个正在等待在该条件变量上的协程发送通知
  • Broadcast: 发送通知(广播),给正在等待该条件变量的所有协程发送通知,容易产生 惊群

示例:

func main() {

	cond := sync.NewCond(&sync.Mutex{})

	condition := false

	// 开启一个新的协程,修改变量 condition
	go func() {

		time.Sleep(time.Second * 1)
		cond.L.Lock()

		condition = true	// 状态变更,发送通知
		cond.Signal()		// 发信号

		cond.L.Unlock()
	}()

	// main协程 是被通知的对象,等待通知
	cond.L.Lock()
	for !condition {
		cond.Wait()			// 内部释放了锁(释放后,子协程能拿到锁),并等待通知(消息)
		fmt.Println("获取到了消息")
	}
	cond.L.Unlock()			// 接到通知后,会被再次锁住,所以需要在需要的场合释放

	fmt.Println("运行结束")
}

使用条件变量优化生产消费模型(支持多个生产者、多个消费者):

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

// 定义缓冲区大小
const BUFLEN = 5

// 全局位置定义全局变量
var cond *sync.Cond = sync.NewCond(&sync.Mutex{})

// 生产者
func producer(ch chan<- int) {
	for {
		cond.L.Lock()           // 给条件变量对应的互斥锁加锁
		for len(ch) == BUFLEN { // 缓冲区满,则等待消费者消费,这里不能是if
			cond.Wait()
		}
		ch <- rand.Intn(1000) // 写入缓冲区一个随机数
		cond.L.Unlock()       // 生产结束,解锁互斥锁
		cond.Signal()         // 一旦生产后,就唤醒其他被阻塞的消费者
		time.Sleep(time.Second * 2)
	}
}

// 消费者
func consumer(ch <-chan int) {
	for {
		cond.L.Lock()      // 全局条件变量加锁
		for len(ch) == 0 { // 如果缓冲区为空,则等待生产者生产,,这里不能是if
			cond.Wait() // 挂起当前协程,等待条件变量满足,唤醒生产者
		}
		fmt.Println("Receive:", <-ch)
		cond.L.Unlock()
		cond.Signal()
		time.Sleep(time.Second * 1)
	}
}

func main() {

	rand.Seed(time.Now().UnixNano()) // 设置随机数种子

	// 生产消费模型中的
	ch := make(chan int, BUFLEN)

	// 启动10个生产者
	for i := 0; i < 10; i++ {
		go producer(ch)
	}

	// 启动10个消费者
	for i := 0; i < 10; i++ {
		go consumer(ch)
	}

	// 阻塞主程序退出
	for {

	}
}

posted @ 2020-06-13 16:37  cool2feel  阅读(214)  评论(0)    收藏  举报