amtoaer

晓风残月

叹息似的渺茫,你仍要保存着那真!
github
x
telegram
steam
nintendo switch
email

Go中sync.cond的應用場景(含舉例及對比)

最近在學習Go 語言高性能編程時看到了sync.Cond條件變量這個概念,一時有些難以理解。查閱資料後對其有了些認知,特此記錄。

概念#

sync包提供了條件變量類型sync.Cond,它可以和互斥鎖或讀寫鎖組合使用,用來協調想要訪問共享變量的協程。其主要作用機制是在對應的共享資源狀態發生變化時,通知其它因此而阻塞的協程。

定義#

sync.Cond是一個結構體,其定義如下:

// Each Cond has an associated Locker L (often a *Mutex or *RWMutex),
// which must be held when changing the condition and
// when calling the Wait method.
//
// A Cond must not be copied after first use.
type Cond struct {
        noCopy noCopy

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

        notify  notifyList
        checker copyChecker
}

該類型可通過sync包中的func NewCond(l Locker) *Cond方法創建。

它有如下三個方法:

  • func (c *Cond) Broadcast()

    喚醒所有等待 c 的協程。

  • func (c *Cond) Signal()

    喚醒單個等待 c 的協程(如果有的話)。

  • func (c *Cond) Wait()

    自動解鎖 c.L並暫停該協程執行,直到被喚醒。該函數在返回前會重新對c.L加鎖。

應用場景#

由以上描述可知,該結構的主要用法應該是多個協程調用Wait等待某個協程運行,該協程任務完畢後調用BroadcastSignal將等待的協程喚醒。即:多用在多個協程等待,一個協程通知的場景

舉個生活中的小例子:

多位同學到達食堂,此時食堂還未做完飯,同學們需要等待食堂出餐後才能恰飯。

Go條件變量對以上場景進行描述,可寫出如下代碼:

package main

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

var (
	ok      = false                           // 是否做完飯
	food    = ""                              // 飯的名字
	rwMutex = &sync.RWMutex{}                 // 讀寫鎖,用於鎖定飯名的修改
	cond    = sync.NewCond(rwMutex.RLocker()) // 條件變量使用讀寫鎖中的讀鎖
)

func makeFood() {
	// 做飯使用寫鎖(當然因為只有一個做飯協程,該鎖並無實際意義)
	rwMutex.Lock()
	defer rwMutex.Unlock()
	fmt.Println("食堂開始做飯!")
	time.Sleep(3 * time.Second)
	ok = true
	food = "魚香肉絲"
	fmt.Println("食堂做完飯了!")
	cond.Broadcast()
}

func waitToEat() {
	cond.L.Lock()
	defer cond.L.Unlock()
	for !ok {
		cond.Wait()
	}
	fmt.Println("總算吃到飯了,這頓吃的是", food)
}

func main() {
	for i := 0; i < 3; i++ {
		go waitToEat()
	}
	go makeFood()
	time.Sleep(4 * time.Second)
}

運行該程序,結果如下:

食堂開始做飯!
食堂做完飯了!
總算吃到飯了,這頓吃的是 魚香肉絲
總算吃到飯了,這頓吃的是 魚香肉絲
總算吃到飯了,這頓吃的是 魚香肉絲

與其他概念的區別#

channel#

眾所周知,channel同樣是 Go 中用於協程同步的概念,但與sync.Cond不同,channel多用於一對一通信,如果繼承上面的例子,這次的場景應該是:

一位同學到達食堂,此時食堂還未做完飯,該同學需要等待食堂出餐後才能恰飯。

channel描述為:

package main

import (
	"fmt"
	"time"
)

var (
	food = ""
	ch   = make(chan struct{})
)

func makeFood() {
	fmt.Println("食堂開始做飯!")
	time.Sleep(3 * time.Second)
	food = "魚香肉絲"
	fmt.Println("食堂做完飯了!")
	ch <- struct{}{}
}

func waitToEat() {
	<-ch
	fmt.Println("總算吃到飯了,這頓吃的是", food)
}

func main() {
	go waitToEat()
	go makeFood()
	time.Sleep(4 * time.Second)
}

運行結果為:

食堂開始做飯!
食堂做完飯了!
總算吃到飯了,這頓吃的是 魚香肉絲

sync.WaitGroup#

sync.WaitGroup同樣用於協程同步,但應用場景與sync.Cond剛好相反,後者多用於多協程等待,單協程通知,而前者多用於單協程等待多協程執行完畢

仍然使用上述例子:

一位同學到達食堂,此時食堂多個窗口均未做完飯,該同學想要等待每個窗口都做完飯後各點一份吃。(多少有點離譜了 XD)

代碼描述如下:

package main

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

var (
	wg sync.WaitGroup
)

func makeFood() {
	fmt.Println("窗口開始做飯!")
	time.Sleep(3 * time.Second)
	fmt.Println("窗口做完飯了!")
	wg.Done()
}

func waitToEat() {
	wg.Wait()
	fmt.Println("oho,每個窗口來一份!")
}

func main() {
	for i := 0; i < 3; i++ {
		wg.Add(1)
		go makeFood()
	}
	go waitToEat()
	time.Sleep(4 * time.Second)
}

運行結果:

窗口開始做飯!
窗口開始做飯!
窗口開始做飯!
窗口做完飯了!
窗口做完飯了!
窗口做完飯了!
oho,每個窗口來一份!

結語#

以上為個人的淺薄理解,如有錯誤歡迎在評論區指出,感謝!

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。