«

Go中sync包Cond使用场景是什么

时间:2024-8-4 06:36     作者:韩俊     分类: Go语言


本篇内容介绍了“Go中sync包Cond使用场景是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

    背景

    编写代码过程中, 通常有主协程和多个子协程进行协作的过程,比如通过 WaitGroup 可以实现当所有子协程完成之后, 主协程再继续执行。

    如上的场景是主协程等待子协程达到某个状态再继续运行。 但是反过来怎么操作呢,要求一组子协程等待主协达到某个状态时才继续运行。这个时候就需要用到 Cond 了

    Cond 简介

    Cond 是和某个条件相关,在条件还没有满足的时候,所有等待这个条件的协程都会被阻塞住,只有这个条件满足的时候,等待的协程才可能继续进行下去。
    Cond 在初始化的时候,需要关联一个 Locker 接口的实例,一般会使用 Mutex 或者 RWMutex。
    Cond 关联的 Locker 实例可以通过 c.L 访问,它内部维护着一个先入先出的等待队列。
    Cond 分别有三个方法

      Wait

    会把当前协程放入Cond的等待队列中并阻塞,直到被Signal或者Broadcast方法从等待队列中移除并唤醒,用于子协程阻塞。

      Signal

    主协程唤醒等待队列中的一个子协程,先唤醒最先阻塞的子协程,被唤醒的子协程继续执行。

      Broadcast

    主协程唤醒等待队列中的全部协程,所有子协程继续执行。

    注意:调用Signal和Broadcast方法,不强求持有c.L的锁,调用Wait方法是必须要持有c.L的锁。

    使用示例

    Signal的使用场景

    大家都去医院先排队,然后等待叫号,先排队的先叫号。这次模拟有5个病人,分别先排队。 然后护士根据排队先后来叫号;
    具体场景是,5个病人在三秒中之内分别排号,护士今天要叫5个号,一秒叫一个,叫完5个号就结束了
    代码如下:

    package main
    import (
        "fmt"
        "math/rand"
        "sync"
        "time"
    )
    
    func main() {
        c := sync.NewCond(&sync.Mutex{})
        num := 0
        // 当前叫号是几号
        hand_num := 0
        for i := 0; i < 5; i++ {
            go func(i int) {
                // 分别在不同时间排队
                time.Sleep(time.Second * time.Duration(rand.Int63n(10)))
                c.L.Lock()
                num++
                // 当前取得号。
                cur := num
                fmt.Printf("%s  %d 号病人取到了 %d 号
    ", time.Now().Format("2006-01-02 15:04:05"), i, cur)
                // 取到号了,等待叫号
                c.Wait()
                fmt.Printf("%s  %d 号病人排队号是 %d 号,被叫号了
    ", time.Now().Format("2006-01-02 15:04:05"), i, cur)
                hand_num = cur
                c.L.Unlock()
            }(i)
        }
    
        // 都叫号了
        for hand_num != 5 {
            // 叫号
            c.Signal()
            time.Sleep(time.Second * 1)
        }
    
        time.Sleep(time.Second * 10)
    }

    执行结果如下

    结果表明,5个病人,分别在三秒钟内先后取号, 然后护士每过一秒钟按照排队的先后顺序叫一个号(叫号的过程依然有病人取号),先取号的被先叫号。
    此场景中,5个病人相当于5个协程, 主协程反复使用

    Signal()
    按照顺序一个个唤醒阻塞的子协程。

    Broadcast的使用场景

    场景为如下: 运动员跑步比赛,要求8秒内全部运动员准备好,然后等待教练发令, 教练10秒后发令,所有运动员在发令后开始跑。

    package main
    
    import (
        "fmt"
        "math/rand"
        "sync"
        "time"
    )
    
    func main() {
        c := sync.NewCond(&sync.Mutex{})
    
        for i := 0; i < 10; i++ {
            go func(i int) {
                // 随机一个8秒内的准备时间
                time.Sleep(time.Second * time.Duration(rand.Int63n(8)))
                fmt.Printf("%s 运动员%d已准备就绪
    ", time.Now().Format("2006-01-02 15:04:05"), i)
                c.L.Lock()
                // 准备完毕,等待教练发令
                c.Wait()
                c.L.Unlock()
                fmt.Printf("%s 运动员%d开跑
    ", time.Now().Format("2006-01-02 15:04:05"), i)
            }(i)
        }
    
        // 主协程等待10秒后发令
        time.Sleep(time.Second * 10)
        fmt.Printf("%s 教练发令。
    ", time.Now().Format("2006-01-02 15:04:05"))
        // 教练发令。通知所有运动员开始跑步, 即唤起之前 wait()的所有协程
        c.Broadcast()
        // 等待跑步
        time.Sleep(time.Second * 5)
    }

    执行结果如下:

    如结果所示, 10个运动员在8秒内分别准备好,等待教练发令后,同时开跑。
    此场景中,10个运动员相当于10个协程, 同时等待主协程的命令,使用

    Broadcast()
    唤醒所有阻塞的子协程。

    注意事项

    使用 Cond,最容易踩的坑就是调用

    Wait()
    方法之前,调用者没有持有锁或没有检查辅助条件。
    在如上示例代码中,假如把调用
    Wait()
    方法前后的加锁和释放锁的代码注释掉,运行代码会导致程序 panic。原因是调用 Wait 方法,会先把调用者放入等待队列中,然后释放锁。此时如果在未持有锁时调用释放锁的方法,就会导致程序 panic。

    标签: golang

    热门推荐