GoLang之抢占式调度一(为什么一个空的for循环就阻塞了程序?Go1.13)

本文探讨了Go语言在1.13版本中遇到的抢占式调度问题,即一个空的for循环导致程序阻塞。分析表明,这是由于依赖栈增长检测的抢占机制在没有函数调用的循环中失效。在Go1.14及以后的版本中,这个问题得到了解决,引入了真正的抢占式调度。文章通过调试工具dlv深入分析了阻塞原因,并详细解释了stoptheworld过程和GC的工作原理。
摘要由CSDN通过智能技术生成

GoLang之抢占式调度一(为什么一个空的for循环就阻塞了程序?Go1.13)

1.Go1.14持续输出

我们来看这样一段代码,只是创建了一个不断输出递增数值的协程,然后 用一个空的for循环等待,接下来我们就通过这段代码来理解,

在这里插入图片描述

go语言的抢占式调度按理说这个程序运行起来应该会不断的输出递增数字,实际上在Go1.14以及以后的版本中确实如此,

在这里插入图片描述

2.Go1.13阻塞

但是在Go1.13的时候,它居然阻塞了,运行环境是双核CPU,执行top命令查看,发现程序仍处在运当中,却没有再继续输出,看来是负责输出数字的goroutine阻塞了,下面我们通过go语言的调试工具dlv来调试一下,

在这里插入图片描述
在这里插入图片描述

2.分析Go1.13阻塞

首先拿到程序对应的进程ID,dlv attack到当前进程,

在这里插入图片描述

通过grs查看一下当前所有的协程,

在这里插入图片描述

1号goroutine对应的main.go的12行,也就是这个空的for 循环,

在这里插入图片描述

剩下的协程里只有7号协程是我们创建的,它当前停在了main.go 第九行,也就是说fmt.printn这里阻塞了。在1号协程前面有一个小星星,代表当前调试工具绑定到了1号协程

下面输入"gr 6"与"grs"切换到6号协程查看一下栈回溯,看看究竟阻塞在哪里,即通过gr命令切换到六号协程。

在这里插入图片描述

再通过bt命令看一下栈回溯,
可以看到实际阻塞发生在runtime.futex 这里,再往上倒,会找到runtime,gcStart,阻塞发生在gcstart所在文件的1287行,定位到这一行,发现它是在执行stop the world(STW)时发生了阻塞。

在这里插入图片描述
在这里插入图片描述

我们在介绍go 语言GC时已经知道GC开始前需要stop the world(SWT) 来进行开启写屏障等准备工作。所以stop the world (SWT)就是要抢占所有的P,让他们暂时放下手里的活,让GC得以正常工作。

在这里插入图片描述

而我们的程序中1号协程没能被抢占,它一直在执行。而stop the world(SWT)一直在等待它让出,这样就陷入了僵局。下面我们就来看看为何程序会陷入这样的局面。

在这里插入图片描述

首先我们来梳理一下stop the world (SWT)的主要逻辑。GC 需要抢占所有的P,但这不是值日生喊一嗓子就能清场的问题,所以它会记录下自己要等待多少个P让出(sched.stopwait=gomaxprocs)。当这个值减为0,目的就达到了。

在这里插入图片描述

对于当前P,以及陷入系统调用中的P(Psyscall),还有空闲状态的P, 直接将它们设置为_Pgcstop状态即可。

在这里插入图片描述

对于还有G在运行的P, 则会将其对应的g.stackguard0设置为一个特殊标识(runtime.stackPreempt),告诉它GC 正在等待你让出呢。

在这里插入图片描述

此外,还会设置一个gcwaiting标识(sched.gcwaiting=1),接下来就通过这两个标识符的配合来实现运行中P的强占。怎么实现呢?

在这里插入图片描述

我们知道goroutine创建之初,栈的大小是固定的。为了防止出现栈溢出的情况,编译器会在有明显栈消耗的函数头部插入一些检测代码。通过g.stackguard0来判断是否需要进行栈增长。但如果这一点g.stackguard0被设置为特殊标识runtime.stackPreempt, 便不会去执行栈增长,而是执行一次调度(schedule())。

在这里插入图片描述

在调度执行时会检测gcwaiting标识,

在这里插入图片描述

若发现GC在等待执行,便会让出当前P, 将其置为_Pgcstop状态。
在这里插入图片描述
这样看来,1号协程之所以没能让出,是因为空的for循环并没有调用函数,也就没机会执行栈增长检测代码。所以他并不知道GC 在等待他让出,这是Go1.13中的情况。依赖栈增长检测代码的方式,不算是真正的抢占式调度。不过,在1.14中,我们迎来了真正意义上的抢占式调度。

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoGo在努力

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值