golang sync包条件等待 sync.Cond

lock.png

前一段时间在看某个开源项目的 util 包时,发现一个模块是基于 sync.Cond 开发的

经过包装一下,的的确确能够解决一些业务中的优化场景

doc

基于互斥锁 sync.Mutex 的衍生,核心作用是让多个 goroutine 等待某个 "条件满足",或通知其他等待的 goroutine "条件已满足"

我理解的 Cond 并没有实现新的功能,仅仅是在 Mutex 上的语法糖封装

coding

在一个消息通知系统中,需要发送邮件给一组人,发送完毕后,再执行后面任务,发送邮件是一个很慢的过程

使用 Mutex 可以这么写

func TestSyncMutex(t *testing.T) {
    var mu sync.Mutex

    go func() { // 邮件发送任务
        mu.Lock()
        fmt.Println("✅ 邮件发送中")
        mu.Unlock()
    }()

    time.Sleep(1 * time.Second) // 让上面的子协程先拿到锁
    mu.Lock()
    fmt.Println("✅ 执行邮件发送完毕后的业务")
    mu.Unlock()
}

但这里面有个很大隐患,就是在必须要先确保让邮件发送任务拿到锁,通过在主线程睡眠方式太消耗性能

这种方式直接❌

改造成 chan 试一下

func TestSyncChan(t *testing.T) {
    state := make(chan struct{})

    go func() { // 邮件发送任务
        fmt.Println("✅ 邮件发送中")
        state <- struct{}{}
    }()

    <-state
    fmt.Println("✅ 执行邮件发送完毕后的业务")
}

这种方式完全没问题✅,解题的思路有多种,再通过 WaitGroup 试一下

func TestSyncWait(t *testing.T) {
    wg := sync.WaitGroup{}
    wg.Add(1)
    go func() { // 邮件发送任务
        defer wg.Done()
        fmt.Println("✅ 邮件发送中")
    }()

    wg.Wait()
    fmt.Println("✅ 执行邮件发送完毕后的业务")
}

也是没问题的✅

Cond coding

文档中介绍,Cond 是实现条件满足通知其它(>=1)线程

我的理解是,在不使用Cond前,你能想办法在其它线程阻塞等待子线程的信号或者说锁状态,就可以实现业务,例如

  • 条件满足,那你自己加自己的业务就是自己的条件满足
  • 其它等待的所有线程,你可以把锁或chan放map、list来实现

但既然标注库实现了Cond,那它的语法糖要更加优雅一点

我将它sync.Cond的使用比作是 React 中的 useState 钩子,用于订阅/通知模型,更加容易理解一些

func TestSyncCond(t *testing.T) {
    var mu sync.Mutex
    cond := sync.NewCond(&mu)

    emialDoneState := false

    go func() { // 邮件发送任务
        fmt.Println("✅ 邮件发送中")

        mu.Lock() // 这里的锁纯粹是保障emialDoneState安全的
        emialDoneState = true
        cond.Broadcast()
        mu.Unlock()
    }()

    mu.Lock() // 这里的锁是配合Cond的
    for !emialDoneState {
        cond.Wait() // 这里不是for-loop 等待,是系统挂起
    }
    mu.Unlock()
    fmt.Println("✅ 执行邮件发送完毕后的业务")
}

执行逻辑图例如下


QQ截图20251209114733.png

核心方法说明

cond.Wait

这是灵魂,他会将Lock解锁,并将程序挂起等待,本质也是一种阻塞等待,替代for循环不断读取emialDoneState变量

cond.Broadcast

告诉所有Wait可以激活了,信号通知

cond.Signal

告诉一个Wait可以激活了,信号通知

当多个任务都在等待某个子任务完成时,Cond的语法就更加优雅一些了

Cond 的几个误区

  1. 当多个cond.Wait时,执行广播Signal/Broadcast时,哪个等待者先执行

cond在发送通知后,例如释放1个,标注库注释中没有特别说明,多个等待者谁先执行,没有顺序这一说,如想保证顺序,自己写业务

  1. sync.Cond完全替代互斥锁

Cond只是对互斥锁多了几个语法糖,本质还是通过lock来实现,我并不喜欢,正如 sync 包作者的开头说明一样

Other than the Once and WaitGroup types, most are intended for use by low-level library routines.
Higher-level synchronization is better done via channels and communication.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容