Golang sync.Cond 简介与用法

核能气质少年 提交于 2019-12-05 01:55:10

1.简介

sync.Cond 实现了一个条件变量,在 Locker 的基础上增加了一个消息通知的功能,其内部维护了一个等待队列,队列中存放的是所有等待在这个 sync.Cond 的 Go 程,即保存了一个通知列表。sync.Cond 可以用来唤醒一个或所有因等待条件变量而阻塞的 Go 程,以此来实现多个 Go 程间的同步。sync.Cond 的定义及成员函数如下:

type Cond struct {
    // L is held while observing or changing the condition
	L Locker
	// contains filtered or unexported fields
}

//创建一个带锁的条件变量,Locker 通常是一个 *Mutex 或 *RWMutex
func NewCond(l Locker) *Cond

//唤醒所有因等待条件变量 c 阻塞的 goroutine
func (c *Cond) Broadcast()

//唤醒一个因等待条件变量 c 阻塞的 goroutine
func (c *Cond) Signal()

//自动解锁 c.L 并挂起 goroutine。只有当被 Broadcast 和 Signal 唤醒,Wait 才能返回,返回前会锁定 c.L
func (c *Cond) Wait()

注意:在调用 Signal,Broadcast 之前,应确保目标 Go 程进入 Wait 阻塞状态。

条件变量并不是被用来保护临界区和共享资源的,它是用于协调想要访问共享资源的那些线程的。当共享资源的状态发生变化时,它可以被用来通知被互斥锁阻塞的线程。

条件变量在这里的最大优势就是在效率方面的提升。当共享资源的状态不满足条件的时候,想操作它的线程再也不用循环往复地做检查了,只要等待通知就好了。

例如同学聚会在 KTV 包间唱歌,假如只有一个麦克风,因为麦克风这类共享资源同一时间内只允许一个人使用,当你的同桌正在拿着麦克风唱“同桌的你”,此时你想唱“老男孩”,那么你的选择可以是:
(1)一直盯看着他,发现他唱完了,立马接过麦克风;
(2)找个地方静静等待(虽然啥也做不了,但不用盯着),等他用完后告诉你一声。

2.要点

(1)Cond 不能被复制。
因为 Cond 内部维护着一个所有等待在这个 Cond 的 Go 程队列,如果 Cond 允许值传递,则这个队列在值传递的过程中会进行复制,导致在唤醒 Go 程的时候出现错误。

(2)唤醒顺序。
从等待队列中按照顺序唤醒,先进入等待队列,先被唤醒。

3.使用示例

package main

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

var locker = new(sync.Mutex)
var cond = sync.NewCond(locker)

func main() {
    for i := 0; i < 10; i++ {
        go func(x int) {
            cond.L.Lock()         //获取锁
            defer cond.L.Unlock() //释放锁
            cond.Wait()           //等待通知,阻塞当前 goroutine
            
            // do something. 这里仅打印
            fmt.Println(x)
        }(i)
    }   
    time.Sleep(time.Second * 1)	// 睡眠 1 秒,等待所有 goroutine 进入 Wait 阻塞状态
    fmt.Println("Signal...")
    cond.Signal()               // 1 秒后下发一个通知给已经获取锁的 goroutine
    time.Sleep(time.Second * 1)
    fmt.Println("Signal...")
    cond.Signal()               // 1 秒后下发下一个通知给已经获取锁的 goroutine
    time.Sleep(time.Second * 1)
    cond.Broadcast()            // 1 秒后下发广播给所有等待的goroutine
    fmt.Println("Broadcast...")
    time.Sleep(time.Second * 1)	// 睡眠 1 秒,等待所有 goroutine 执行完毕
}

多次运行结果不一致,示例输出:

Signal...
4
Signal...
0
Broadcast...
7
1
2
3
5
9
6
8

注意:
(1)调用 Wait() 函数前,需要先获得条件变量的成员锁,原因是需要互斥地变更条件变量的等待队列。在 Wait() 返回前,会重新上锁。
(2)条件变量和锁结合使用,在并发时如果逻辑不严谨容易发生死锁,所以尽量不要使用条件变量,推荐用 sync.WaitGroup 来实现并发时 Go 程间的同步。

参考文章

[1] Package sync.Cond
[2] Golang Cond源码分析
[3] GO 条件锁sync.Cond(1)
[4] GO 条件锁sync.Cond(2)

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!