golang实现等待通知机制的方法

参考博客:

https://geektutu.com/post/hpg-sync-cond.html

https://cyent.github.io/golang/goroutine/sync_cond/

使用sync.Cond实现

如果想在使用 Cond 的时候避免犯错,只要时刻记住**调用 cond.Wait 方法之前一定要加锁**,以及**waiter goroutine 被唤醒不等于等待条件被满足**这两个知识点。
package awaitnotify

import (
    "fmt"
    "math/rand"
    "sync"
    "testing"
    "time"
)

func init() {
    rand.Seed(time.Now().UnixNano())
}

// 使用Cond实现一个等待/通知机制
func TestWN1(t *testing.T) {

    c := sync.NewCond(&sync.Mutex{})
    var ready int

    // 10名运动员
    for i := 0; i < 10; i++ {
        go func(num int) {
            // 模拟运动员等待时间
            time.Sleep(time.Duration(rand.Intn(5)) * time.Second)

            // 非原子操作加锁保护
            c.L.Lock()
            ready++
            c.L.Unlock()

            fmt.Printf("运动员 %d 已经就绪!\n", num)

            // 广播唤醒所有等待者
            // c.Broadcast()

            // 唤醒其中一个等待者,因为只有一个等待者,所以用哪个方法都行
            c.Signal()
        }(i)
    }

    // 1、调用Wait方法一定要持有 c.L 的锁
    c.L.Lock()
    // 2、需要判断条件,否则一个子协裎就能让逻辑往下走
    for ready != 10 {
        c.Wait()
        fmt.Println("裁判员被唤醒一次(有一个运动员已经准备好了)!")
    }
    c.L.Unlock()

    // 所有运动员准备就绪
    fmt.Println("所有运动员准备就绪!开始比赛!")
}
~~~

使用Waitgroup实现(纯等待的机制可以使用,没有自带通知机制)

本质上 WaitGroup 和 Cond 是有区别的:WaitGroup 是主 goroutine 等待确定数量的子 goroutine 完成任务;而 Cond 是等待某个条件满足,这个条件的修改可以被任意多的 goroutine 更新,而且 Cond 的 Wait 不关心也不知道其他 goroutine 的数量,只关心等待条件。而且 Cond 还有单个通知的机制,也就是 Signal 方法。

使用channel实现

清理正常 

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {

    var closing = make(chan struct{}) // 代表程序退出,但是清理工作还没做
    var closed = make(chan struct{})  // 代表清理工作已经做完

    go func() {
        // 模拟业务处理
        for {
            select {
            case <-closing:
                fmt.Println("程序退出了.....")
                return
            default:
                // ....... 业务计算
                time.Sleep(1 * time.Second)
                fmt.Println("业务计算........")
            }
        }
    }()

    // 处理CTRL+C等中断信号
    // Notice 注意需要带缓存的Channel
    termChan := make(chan os.Signal, 1)
    signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)
    <-termChan // 一直等着

    close(closing)
    // 执行退出之前的清理动作
    go doCleanup(closed)

    select {
    case <-closed: // 一直等着
    case <-time.After(time.Second * 2): // 只等2秒,清理时间超过2秒就走这里
        fmt.Println("清理超时,不等了")
    }
    fmt.Println("优雅退出")
}

func doCleanup(closed chan struct{}) {
    // Notice 清理的时间
    time.Sleep((time.Second))
    close(closed)
    fmt.Println("清理完毕!")
}

清理超时

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {

    var closing = make(chan struct{}) // 代表程序退出,但是清理工作还没做
    var closed = make(chan struct{})  // 代表清理工作已经做完

    go func() {
        // 模拟业务处理
        for {
            select {
            case <-closing:
                fmt.Println("程序退出了.....")
                return
            default:
                // ....... 业务计算
                time.Sleep(1 * time.Second)
                fmt.Println("业务计算........")
            }
        }
    }()

    // 处理CTRL+C等中断信号
    // Notice 注意需要带缓存的Channel
    termChan := make(chan os.Signal, 1)
    signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)
    <-termChan // 一直等着

    close(closing)
    // 执行退出之前的清理动作
    go doCleanup(closed)

    select {
    case <-closed: // 一直等着
    case <-time.After(time.Second * 2): // 只等2秒,清理时间超过2秒就走这里
        fmt.Println("清理超时,不等了")
    }
    fmt.Println("优雅退出")
}

func doCleanup(closed chan struct{}) {
    // Notice 清理的时间
    time.Sleep((time.Minute))
    close(closed)
    fmt.Println("清理完毕!")
}

sync.Cond与channel的比较

绝大多数情况下我们使用channel就能实现需求,而使用Cond的话坑会比较多,

但是,还是有一批忠实的“粉丝”坚持在使用 Cond,原因在于 Cond 有三点特性是 Channel 无法替代的:

 

  • Cond 和一个 Locker 关联,可以利用这个 Locker 对相关的依赖条件更改提供保护。

  • Cond 可以同时支持 Signal 和 Broadcast 方法,而 Channel 只能同时支持其中一种。这个是Cond存在的核心,结合 Cond 可以重用,才是下面k8s用Cond而不用Channel的原因

  • Cond 的 Broadcast 方法可以被重复调用。等待条件再次变成不满足的状态后,我们又可以调用 Broadcast 再次唤醒等待的 goroutine。这也是 Channel 不能支持的,Channel 被 close 掉了之后不支持再 open。

 

~~~

posted on 2022-12-18 10:23  江湖乄夜雨  阅读(571)  评论(0编辑  收藏  举报