乐趣区

关于golang:syncCond学习笔记

sync.Cond

go/sync

sync.Cond 用于 goroutine 之间的合作, 用于协程的挂起和唤醒。当多个协程之间合作时,有可能一个协程须要依赖别的协程实现后再进行某种操作,这时可根据管道进行通信,这对于两个协程之间是比拟不便的。但若是多个协程依赖一个协程的实现后再进行某种操作,那么就能够应用 sync.Cond 来实现了;

例子:

package main

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

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

func main() {
    for i := 0; i < 40; i++ {go func(x int) {cond.L.Lock()         // 获取锁
            cond.Wait()           // 期待告诉, 阻塞以后 goroutine
              cond.L.Unlock()       // 开释锁
            fmt.Println(x)
            time.Sleep(time.Second * 1)

        }(i)
    }
    time.Sleep(time.Second * 1)
    fmt.Println("Signal...")
    cond.Signal() // 下发一个告诉给曾经获取锁的 goroutine
    time.Sleep(time.Second * 1)
    cond.Signal() // 3 秒之后 下发一个告诉给曾经获取锁的 goroutine
    time.Sleep(time.Second * 3)
    cond.Broadcast() // 3 秒之后 下发播送给所有期待的 goroutine
    fmt.Println("Broadcast...")
    time.Sleep(time.Second * 60)
}

协程在应用 cond 时,首先须要对 cond 持有的锁进行加锁操作,而后调用 Wait 函数期待,后续操作会阻塞在 Wait 处;当主协程决定唤醒期待的协程时,它会应用 Signal(随机唤醒一个期待的协程)或 Broadcast(唤醒所有期待的协程)唤醒期待的协程。当某一协程被唤醒后,Wait 函数会返回,此时须要解锁,之后便能够做后续的操作了;

实现细节:
cond 的数据结构

type Cond struct {
    noCopy noCopy

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

    notify  notifyList
    checker copyChecker
}

Cond 构造体持有一把锁,这个锁用来做协程间的同步操作;notify 是一个唤醒队列,所有调用过 Wait 办法的协程都会被退出到这个唤醒队列中

Wait 函数

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

首先,以后协程会被退出到 notifyList 中,之后会解锁 L,这是因为以便让别的协程能够获取到锁,做同样的操作(lock, wait, unlock)中的 lock 这一步;之后 runtime_notifyListWait 会阻塞,即所有调用过 Wait 函数的协程都会阻塞在这个中央;
之后若该协程被唤醒,那么 runtime_notifyListWait 会返回,此时又回从新给 L 加锁,而后第一个抢占到这个返回操作的协程,它的 Wait 操作真正返回了;这个协程的 Wait 函数内部,可能后续还会做一些须要互斥的操作,在这个协程做完所有操作后,依照规定它会调用 Unlock 解锁,这时别的协程的 Wait 函数能力真正的返回,做后续操作;

对于唤醒函数就比较简单了:

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

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

从队列中唤醒即可

退出移动版