golang_条件变量:sync.Cond作为条件变量的使用

场景:

生产者消费者模型中(比如一个有缓存的channel中),生产者进行生产之前去抢锁,如果缓存已满,生产者抢锁无效;消费者进行消费之前去抢锁,如果缓存为空,消费者抢锁无效。所有在抢锁之前需要进行一个判断,称之为条件变量。

sync.Cond的结构:

type Cond struct {
	noCopy noCopy

	// L is held while observing or changing the condition
	L Locker

	notify  notifyList
	checker copyChecker
}

其中有一把锁。

sync.Cond结构体实现的方法:

Wait() // 阻塞-加锁-解锁
Wait()一旦阻塞后,就需要唤醒才能进行抢锁,即需要Signal()或Broadcast()进行唤醒。

func (c *Cond) Wait() {
	c.checker.check()
	t := runtime_notifyListAdd(&c.notify)
	c.L.Unlock()
	runtime_notifyListWait(&c.notify, t)
	c.L.Lock()
}

Signal() // 唤醒一个goroutine

func (c *Cond) Signal() {
	c.checker.check()
	runtime_notifyListNotifyOne(&c.notify)
}

Broadcast() // 唤醒所有goroutine

func (c *Cond) Broadcast() {
	c.checker.check()
	runtime_notifyListNotifyAll(&c.notify)
}

实际用例

构建一个缓存为3的channel,分别用5个生产者消费者去写读数据。

package main

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

var cond sync.Cond

func producer(in chan<- int) {
	for {
		cond.L.Lock()
		for len(in) == 3 {	// 循环判断缓存是否已满。
		// 这里使用for循环而不是if判断的原因:因为这时缓存已经满了,
		// 再唤醒生产者还是会进入wait的状态,这里想唤醒consumer
		// 进行消费一个缓存,然后才能继续进行下去
			cond.Signal()	// 唤醒之前一个goroutine
			cond.Wait() // 阻塞-解锁-加锁
		}
		num := rand.Intn(100)	// 写入100以内随机的数字
		in <- num
		fmt.Println("生产了", num)
		cond.L.Unlock()
	}
}

func consumer(out <-chan int) {
	for {
		cond.L.Lock()
		for len(out) == 0 {	// 循环判断缓存是否为空
			cond.Signal()
			cond.Wait()
		}
		num := <- out
		fmt.Println("消费了", num)
		cond.L.Unlock()
	}
}

func main() {
	ch := make(chan int, 3)
	cond.L = new(sync.Mutex)	// 需要重新定义锁
	rand.Seed(time.Now().UnixNano())	//  为随机数添加随机种子

	for i := 0; i < 5; i++ {
		go producer(ch)
	}
	for i := 0; i < 5; i++ {
		go consumer(ch)
	}

	select {}	// 阻塞主函数
}

Output:

生产了 22
生产了 82
生产了 33
消费了 22
消费了 82
消费了 33
生产了 81
生产了 86
......
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章