Golang随笔-生活工作点滴

Go 并发实战 -- sync Cond

2019-07-10  本文已影响21人  邹志全

前言

go中的sync.Cond也就是condition,是一个条件同步变量,与Java中Object的wait、notify、notifyAll方法或者Condition类的作用比较类似,如果有这方面的基础学习起来会非常简单。其实Java中的JUC包实现的可以是最丰富和易用的了,熟知JUC的话,学习其他语言的并发特性及工具的话会非常简单。

语法基础

sync.Cond同其他并发条件变量一样,提供了阻塞和唤醒函数:
Wait() 阻塞操作
Signal() 唤醒一个协程
Broadcast() 唤醒所有协程
不同的Cond需要我们制定一把锁,通常是Mutex、RWMytex,当然也可以是你自己实现的锁。
下面来看一下sync.Cond的使用:

func main() {
    lock := &sync.Mutex{}
    cond := sync.NewCond(lock)
    for i:=0; i<10; i++ {
        runGorotine(cond, i)
    }
    time.Sleep(1*time.Millisecond)
    fmt.Println("----------------------------: signal 唤醒单个")
    cond.Signal()
    time.Sleep(1*time.Millisecond)
    fmt.Println("----------------------------: broadcast 唤醒全部")
    cond.Broadcast()
    time.Sleep(2*time.Second)
}

func runGorotine(cond *sync.Cond, i int) {
    go func(cond *sync.Cond, i int) {
        cond.L.Lock()
        for condition() {
            fmt.Println("-goroutine-" + strconv.Itoa(i) + " 命中wait")
            cond.Wait()
        }
        fmt.Println("-goroutine-" + strconv.Itoa(i) + " 命中条件")
        cond.L.Unlock()
    }(cond, i)
}

func condition() bool {
    rand.Intn(50)
    if rand.Intn(50) > 20 {
        fmt.Print(true)
        return true
    }
    fmt.Print(false)
    return false
}

输出:


image.png

ps:go 协程之后启动后并不是立即执行的,需要有一定的分配过程及等待的时间,所以说sleep一小段时间,否则唤醒通知在wait之前发生就没有意义了。
上述就是Cond的最简单的使用,生产环境比这个demo要复杂一些,但是大致也就这样了。

实现原理

Cond的实现非常简单,锁操作依赖的是我们创建的lock。然后依赖于runtime阻塞和唤醒go协程的函数。

type Cond struct {
// 这个已经不是第一次见了,第一次使用后就不能copy了
    noCopy noCopy
    L Locker // 创建cond是传入的锁
    notify  notifyList // 调用runtime阻塞和唤醒的函数,内部维护了一个阻塞链表
    checker copyChecker // 保留指向自身的指针以检测对象复制。
}
// 构造函数
func NewCond(l Locker) *Cond {
    return &Cond{L: l}
}

wait函数的锁逻辑有点奇怪,其实主要是为了把外面条件判断和添加阻塞队列变为一个原子操作,这种锁的使用方式其实不太建议,比较容易出问题。

func (c *Cond) Wait() {
    c.checker.check()
    t := runtime_notifyListAdd(&c.notify) // 添加阻塞列表
    c.L.Unlock() // 函数调用前已经加锁了,在添加阻塞队列后就可以撤销锁了
    runtime_notifyListWait(&c.notify, t) // 进行阻塞
    c.L.Lock() // 外层还有个解锁操作,加把锁
}

下面是唤醒操作:

func (c *Cond) Signal() {
    c.checker.check()
    runtime_notifyListNotifyOne(&c.notify) 
    // 唤醒一个
}

func (c *Cond) Broadcast() {
    c.checker.check()
    runtime_notifyListNotifyAll(&c.notify)
    // 唤醒多个
}

关于Cond的使用及源码实现暂时介绍这么多。

上一篇下一篇

猜你喜欢

热点阅读