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
......
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章