Go 语言核心 36 讲
郝林
《Go 并发编程实战》作者,前轻松筹大数据负责人
79610 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 55 讲
Go 语言核心 36 讲
15
15
1.0x
00:00/00:00
登录|注册

17 | go语句及其执行规则(下)

实现自旋
判断值是否与参数i相同
不断获取count变量的值
传入int类型参数
更好的等待其他goroutine运行完毕的方法
主goroutine从通道接收元素值
在goroutine运行完毕时向通道发送值
创建通道与启用的goroutine数量一致
需要合适的睡眠时间
使主goroutine暂停一段时间
如何使两者顺序一致
与go语句的执行顺序不同
干涉手段:time.Sleep, 通道, sync.WaitGroup
避免过早结束
让count变量成为一个信号
使用原子操作
trigger函数
改造for语句中的go函数
sync.WaitGroup
使用通道
time.Sleep
runtime包中与G、P和M相关的函数
简单、清晰的改造方案
go函数的执行顺序
主goroutine的运行控制
问题2:怎样让启用的多个goroutine按照既定的顺序运行?
问题1:怎样才能让主goroutine等待其他goroutine?
思考题
总结
知识扩展
go语句以及执行规则

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

你好,我是郝林,今天我们继续分享 go 语句执行规则的内容。
在上一篇文章中,我们讲到了 goroutine 在操作系统的并发编程体系,以及在 Go 语言并发编程模型中的地位和作用等一系列内容,今天我们继续来聊一聊这个话题。

知识扩展

问题 1:怎样才能让主 goroutine 等待其他 goroutine?
我刚才说过,一旦主 goroutine 中的代码执行完毕,当前的 Go 程序就会结束运行,无论其他的 goroutine 是否已经在运行了。那么,怎样才能做到等其他的 goroutine 运行完毕之后,再让主 goroutine 结束运行呢?
其实有很多办法可以做到这一点。其中,最简单粗暴的办法就是让主 goroutine“小睡”一会儿。
for i := 0; i < 10; i++ {
go func() {
fmt.Println(i)
}()
}
time.Sleep(time.Millisecond * 500)
for语句的后边,我调用了time包的Sleep函数,并把time.Millisecond * 500的结果作为参数值传给了它。time.Sleep函数的功能就是让当前的 goroutine(在这里就是主 goroutine)暂停运行一段时间,直到到达指定的恢复运行时间。
我们可以把一个相对的时间传给该函数,就像我在这里传入的“500 毫秒”那样。time.Sleep函数会在被调用时用当前的绝对时间,再加上相对时间计算出在未来的恢复运行时间。显然,一旦到达恢复运行时间,当前的 goroutine 就会从“睡眠”中醒来,并开始继续执行后边的代码。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了Go语言中goroutine的执行规则及并发编程应用。作者介绍了让主goroutine等待其他goroutine执行完毕的方法,包括使用time.Sleep函数和通道。此外,还讨论了如何让多个goroutine按照既定顺序运行,通过改造for语句中的goroutine函数和使用trigger函数实现了异步发起的goroutine的同步执行。文章突出了对goroutine执行规则的深入探讨,以及在实际并发编程中的应用技巧。通过本文,读者能够深入理解goroutine及其背后的并发编程模型,从而更加游刃有余地使用`go`语句。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 语言核心 36 讲》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(79)

  • 最新
  • 精选
  • 枫林火山
    老师,关于顺序打印的demo40.go的优化版本,同来碗绿豆汤同学的实现 package main import "fmt" func main() { var num = 10 sign := make(chan struct{}, 1) for i := 0; i < num; i++ { go func(i int) { fmt.Println(i) sign <- struct{}{} }(i) <-sign } } 这样写为什么不能保证同步,能不能再详细解释下呢。这个实现和您demo39.go 相比,只是合并了两处for循环,我看好多同学也有这个疑问,向您求解。 demo40.go的实现相当于实现了每个异步线程的一个轮询loop。 上面的实现相当于单步间加了一个barrier。执行1->等待->执行2->等待。 实在没理解为什么不能保证同步

    作者回复: 我又看了一下“来碗绿豆汤”同学写的代码。我可能当时没看清楚,或者没说清楚。 他写的这段代码单从“顺序打印数字”的要求上看是可以的。但是这样做就变成纯同步的流程了,go函数就完全没必要写了。把go函数中的代码拿出来、删掉go函数,再把通道的相关代码也删掉,岂不是更直截了当?像这样: for i := 0; i < num; i++ { fmt.Println(i) } 这个题目的要求是“使得在for循环中启用的多个goroutine按照既定的顺序运行”。你也可以把它理解为“在异步的情况下顺序的打印数字”。所以,“来碗绿豆汤”同学写的代码只满足了其中一个要求,而没有让go函数们自由的异步执行。 我的那个版本demo40.go是让各个go函数(确切地说,是它们调用的trigger函数)自行地检查所需条件,然后再在条件允许的情况下打印数字。这也叫“自旋”。这与纯同步的流程是有本质上的区别的。

    2019-04-01
    4
    69
  • 来碗绿豆汤
    我有一个更简单的实现方式, 如下 func main(){ ch := make(chan struct{}) for i:=0; i < 100; i++{ go func(i int){ fmt.Println(i) ch <- struct{}{} }(i) <-ch } } 这样,每次循环都包装goroutine 执行结束才进入下一次循环,就可以保证顺序执行了

    作者回复: 这些go函数的真正执行谁先谁后是不可控的,所以这样做不行的。

    2018-09-19
    9
    37
  • Geek_3241ef
    你好,郝老师,请问这里为什么需要sleep呢,我理解的如果不加sleep,其中某个g会一直轮询count的值,当另一个g更改这个值时,那么第一个g就会判断相等才对呀。 但实际上去掉sleep后,程序确实没有按照我理解的逻辑执行,请问这是为什么呢

    作者回复: 这主要是因为:Go 调度器在需要的时候只会对正在运行的 goroutine 发出通知,试图让它停下来。但是,它却不会也不能强行让一个 goroutine 停下来。 所以,如果一条 for 语句过于简单的话,比如这里的 for 语句就很简单(因为里面只有一条 if 语句),那么当前的 goroutine 就可能不会去正常响应(或者说没有机会响应)Go 调度器的停止通知。 因此,这里加一个 sleep 是为了:在任何情况下(如任何版本的 Go、任何计算平台下的 Go、任何的 CPU 核心数等),内含这条 for 语句的这些 goroutine 都能够正常地响应停止通知。

    2019-08-26
    20
  • Askerlve
    package main import ( "fmt" "sync/atomic" ) func main() { var count uint32 trigger := func(i uint32, fn func()) { for { if n := atomic.LoadUint32(&count); n == i { fn() atomic.AddUint32(&count, 1) break } } } for i := uint32(0); i < 10; i++ { go func(i uint32) { fn := func() { fmt.Println(i) } trigger(i, fn) }(i) } trigger(10, func() {}) } 测试了下,这个函数的输出不受控,并且好像永远也不会结束,有人能帮忙解释下吗,go小白~😀

    作者回复: 可以加个sleep

    2018-09-19
    7
    12
  • 老茂
    不加sleep程序不能正常结束的情况貌似跟cpu核数有关,我是4核cpu,打印0到2每次都可以正常执行;0到3以上就会有卡主的情况,卡主时cpu达到100%,load会超过4。猜测是不是此时所有cpu都在处理count==0的for循环,没有空闲的cpu执行atomic.AddUint32(&count, 1)?

    作者回复: Go语言调度goroutine是准抢占式的,虽然会防止某个goroutine运行太久,并做换下处理。但是像简单的死循环这种有可能会换下失败,尤其是windows下,这跟操作系统的底层支持有关。不过一般情况下不用担心。

    2018-10-15
    10
  • 新垣结裤
    func main() { num := 10 chs := [num+1]chan struct{}{} for i := 0; i < num+1; i++ { chs[i] = make(chan struct{}) } for i := 0; i < num; i++ { go func(i int) { <- chs[i] fmt.Println(i) chs[i+1] <- struct{}{} }(i) } chs[0] <- struct{}{} <- chs[num] } 每个goroutine执行完通过channel通知下一个goroutine,在主goroutine里控制第一个goroutine的开始,并接收最后一个goroutine结束的信号

    作者回复: 搞这么多通道有些浪费啊。另外切片不是并发安全的数据类型,最好不要这样用。

    2018-09-21
    8
  • 言午木杉
    这篇加了代码,一下子就容易很多了,老师前面的几篇都太多名词了,需要琢磨去好几遍

    作者回复: 正式学习一个新东西的首要任务就是“重要名词解析”。一旦熟悉了这些名词以及它们背后的深意,后面的学习效率就会高很多。更重要的是,后面会学得更扎实(或者说很稳)。因为你真正融入了这个新东西所处的世界,站在了它的地基之上。这也是我的一点学习经验。共勉。

    2020-01-14
    3
    7
  • Askerlve
    package main import ( "fmt" "sync/atomic" ) func main() { var count uint32 trigger := func(i uint32, fn func()) { for { if n := atomic.LoadUint32(&count); n == i { fn() atomic.AddUint32(&count, 1) break } } } for i := uint32(0); i < 10; i++ { go func(i uint32) { fn := func() { fmt.Println(i) } trigger(i, fn) }(i) } trigger(10, func() {}) } 这个函数的执行还是不可控诶,并且好像永远也不会结束,是因为我的go版本问题吗?

    作者回复: Win下可能会有问题,你在bif语句后边加一句time.sleep(time.Nanosecond)。github上的代码我已经更新了。

    2018-09-19
    7
  • 肖恩
    第一遍看好多都看不懂,看到后边回过头来看,发现用自旋goroutine实现,真实奇妙;现在想想,除了文章中实现方式,可以用channel同步实现;还可以用sync.WaitGroup实现

    作者回复: 祝贺你升级了;)

    2019-05-08
    2
    6
  • 嗷大猫的鱼
    老师,最近从头学习,前面一直没跟着动手,也没自己总结。这几天在整理每章的重点! https://github.com/wenxuwan/go36 刚写完第二章,突然发现自己动手总结和只看差好多。我会继续保持喜欢总结!

    作者回复: 很好,加油!

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