Go 并发编程实战课
晁岳攀(鸟窝)
前微博技术专家,知名微服务框架 rpcx 作者
25635 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 22 讲
Go 并发编程实战课
15
15
1.0x
00:00/00:00
登录|注册

17 | SingleFlight 和 CyclicBarrier:请求合并和循环栅栏该怎么用?

CoreDNS
Cockroachdb
固定数量的goroutine等待同一个执行点
CyclicBarrier interface
NewWithAction
New
groupcache
缓存系统
Forget
DoChan
Do
Group
双氧水制造工厂
一氧化二氢制造工厂
示例项目
应用场景
实现原理
示例项目
应用场景
实现原理
思考题
CyclicBarrier
SingleFlight
并发原语

该思维导图由 AI 生成,仅供参考

你好,我是鸟窝。
这节课,我来给你介绍两个非常重要的扩展并发原语:SingleFlight 和 CyclicBarrier。SingleFlight 的作用是将并发请求合并成一个请求,以减少对下层服务的压力;而 CyclicBarrier 是一个可重用的栅栏并发原语,用来控制一组请求同时执行的数据结构。
其实,它们两个并没有直接的关系,只是内容相对来说比较少,所以我打算用最短的时间带你掌握它们。一节课就能掌握两个“武器”,是不是很高效?

请求合并 SingleFlight

SingleFlight 是 Go 开发组提供的一个扩展并发原语。它的作用是,在处理多个 goroutine 同时调用同一个函数的时候,只让一个 goroutine 去调用这个函数,等到这个 goroutine 返回结果的时候,再把结果返回给这几个同时调用的 goroutine,这样可以减少并发调用的数量。
这里我想先回答一个问题:标准库中的 sync.Once 也可以保证并发的 goroutine 只会执行一次函数 f,那么,SingleFlight 和 sync.Once 有什么区别呢?
其实,sync.Once 不是只在并发的时候保证只有一个 goroutine 执行函数 f,而是会保证永远只执行一次,而 SingleFlight 是每次调用都重新执行,并且在多个请求同时调用的时候只有一个执行。它们两个面对的场景是不同的,sync.Once 主要是用在单次初始化场景中,而 SingleFlight 主要用在合并并发请求的场景中,尤其是缓存场景。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了两个重要的并发原语:SingleFlight和CyclicBarrier。SingleFlight用于合并并发请求,减少对下层服务的压力,适用于缓存系统中,能提高系统性能。其实现原理是使用互斥锁和Map来实现,并在处理多个goroutine同时调用同一个函数时,只让一个goroutine去调用这个函数。CyclicBarrier用于控制一组请求同时执行的数据结构,适合用在“固定数量的goroutine等待同一个执行点”的场景中。它可以被重复使用,不像WaitGroup需要小心翼翼避免panic。文章还介绍了CyclicBarrier的初始化方法和使用方式,并给出了一个经典的并发编程题目,展示了CyclicBarrier的应用场景和功能。最后,文章给出了H2O制造工厂的具体实现,通过代码示例展示了CyclicBarrier的使用方法。整体来说,本文详细介绍了SingleFlight和CyclicBarrier的特点、应用场景和实现原理,对于并发编程感兴趣的读者具有很高的参考价值。文章还提出了思考题,引发读者思考并发编程中的更多可能性。建议读者多了解并发原语,丰富知识库,提升在并发场景中的应对能力。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 并发编程实战课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(20)

  • 最新
  • 精选
  • 杜鑫
    谢谢老师的文章!第一次知道有这两种并发原语,不管之后工作能不能用到,这都是对个人眼界的开阔! ps:老师的文章几乎都是一篇顶两篇,刚订阅的时候还担心课程内容太少,现在一看担心完全是多余的,这段时间读了老师的文章之后,个人对go并发知识了解更深刻了,谢谢老师!

    作者回复: 加油!

    2020-11-19
    13
  • 无名氏
    CyclicBarrier 那个测试例子没有看懂,2个H,一个O顺序怎么保证是HHO😅

    作者回复: hho,hoh,ohh,都可以,只有保证是一个水分子

    2022-03-19
    2
    1
  • Fan
    老师,在你github地址 https://github.com/smallnest/dive-to-gosync-workshop/tree/master/7.orchestration/water中没有搜到WaitGroup版本的H2O的例子,但是按照你正文中WaitGroup版本实现H2O,有报错panic: sync: WaitGroup is reused before previous Wait has returned 。请问老师这应该怎么解决呢?很困惑,望老师指点。

    作者回复: https://github.com/smallnest/dive-to-gosync-workshop/tree/master/7.orchestration/water

    2020-12-28
    2
    1
  • Fan
    老师,感觉你上面用waitGroup实现这个H2O的例子有问题的。我这边运行都panic的。

    作者回复: 报什么错?这些例子都是我运行过的,你也可以到github上搜我的一个项目叫workshop的代码

    2020-12-25
    4
    1
  • 伟伟
    package main import ( "context" "github.com/marusama/cyclicbarrier" "golang.org/x/sync/semaphore" ) type H2O2 struct { semaH *semaphore.Weighted semaO *semaphore.Weighted b cyclicbarrier.CyclicBarrier } func New() *H2O2 { return &H2O2{ semaH: semaphore.NewWeighted(2), semaO: semaphore.NewWeighted(2), b: cyclicbarrier.New(4), } } func (h2o2 *H2O2) hydrogen(releaseHydrogen func()) { h2o2.semaH.Acquire(context.Background(), 1) releaseHydrogen() h2o2.b.Await(context.Background()) h2o2.semaH.Release(1) } func (h2o2 *H2O2) oxygen(releaseOxygen func()) { h2o2.semaO.Acquire(context.Background(), 1) releaseOxygen() h2o2.b.Await(context.Background()) h2o2.semaO.Release(1) }

    作者回复: ������������

    2020-11-23
    1
  • 白开d水
    为什么在 h2o.semaH.Acquire(context.Background(), 1) 1不能换成2呢,直接请求2个资源?

    作者回复: 每个goroutine只能出一个原子

    2023-03-23归属地:北京
    2
  • 李晓清
    package main import ( "context" "fmt" "time" "github.com/marusama/cyclicbarrier" ) var result chan string = make(chan string, 4) var barrier = cyclicbarrier.NewWithAction(4, func() error { fmt.Println(<-result, <-result, <-result, <-result) return nil }) var h = func() { for { time.Sleep(1 * time.Second) result <- "H" barrier.Await(context.TODO()) } } var o = func() { for { time.Sleep(1 * time.Second) result <- "O" barrier.Await(context.TODO()) } } func main() { go h() go h() go o() go o() select {} }

    作者回复: 如果没有tome.sleep,或者sleep很短,还可以吗

    2023-03-17归属地:北京
  • Geek8956
    老师,请问循环栏栅和cond并发原语有什么区别?他们都可以让多个协程等待某个条件满足,然后并发的开始执行。

    作者回复: 循环栅栏的重点是循环,重用

    2021-11-29
  • 老纪
    在CyclicBarrier中,是需要一组goroutine都执行到Await()方法后,才会都向下执行否则就会阻塞在Await()方法上吗

    作者回复: 是的

    2020-12-08
  • Linuxer
    感觉自已Go刚刚入门,我确实也才学习一周左右,看着挺爽,写起来还是不顺手,还得多练习,老师能否给我们这些打算转Go的新手一些建议,谢谢

    作者回复: 多实践,在项目中锻炼,很快就顺手了

    2020-11-20
收起评论
显示
设置
留言
20
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部