Go语言调度器之主动调度(20)
本文是《Go语言调度器源代码情景分析》系列的第20篇,也是第五章《主动调度》的第1小节。
Goroutine的主动调度是指当前正在运行的goroutine通过直接调用runtime.Gosched()函数暂时放弃运行而发生的调度。
主动调度完全是用户代码自己控制的,我们根据代码就可以预见什么地方一定会发生调度。比如下面的程序,在main goroutine中创建了一个新的我们称之为g2的goroutine去执行start函数,g2在start函数的循环中反复调用Gosched()函数放弃自己的执行权,主动把CPU让给调度器去执行调度。
package main import (
"runtime"
"sync"
) const N = 1 func main() {
var wg sync.WaitGroup wg.Add(N)
for i := 0; i < N; i++ {
go start(&wg)
} wg.Wait()
} func start(wg *sync.WaitGroup) {
for i := 0; i < 1000 * 1000 * 1000; i++ {
runtime.Gosched()
} wg.Done()
}
下面我们就从这个程序开始分析主动调度是如何实现的。
首先从主动调度的入口函数Gosched()开始分析。
runtime/proc.go : 262
// Gosched yields the processor, allowing other goroutines to run. It does not
// suspend the current goroutine, so execution resumes automatically.
func Gosched() {
checkTimeouts() //amd64 linux平台空函数 //切换到当前m的g0栈执行gosched_m函数
mcall(gosched_m)
//再次被调度起来则从这里开始继续运行
}
因为我们需要关注程序运行起来之后g2 goroutine的状态,所以这里用gdb配合源代码一起来进行调试和分析,首先使用b proc.go:266在Gosched函数的mcall(gosched_m)这一行设置一个断点,然后运行程序,等程序被断下来之后,反汇编一下程序当前正在执行的函数
(gdb) disass
Dump of assembler code for function main.start:
0x000000000044fc90 <+>:mov %fs:0xfffffffffffffff8,%rcx
0x000000000044fc99 <+>:cmp 0x10(%rcx),%rsp
0x000000000044fc9d <+>:jbe 0x44fcfa <main.start+>
0x000000000044fc9f <+>:sub $0x20,%rsp
0x000000000044fca3 <+>:mov %rbp,0x18(%rsp)
0x000000000044fca8 <+>:lea 0x18(%rsp),%rbp
0x000000000044fcad <+>:xor %eax,%eax
0x000000000044fcaf <+>:jmp 0x44fcd0 <main.start+>
0x000000000044fcb1 <+>:mov %rax,0x10(%rsp)
0x000000000044fcb6 <+>:nop
0x000000000044fcb7 <+>:nop
=> 0x000000000044fcb8 <+>:lea 0x241e1(%rip),%rax # 0x473ea0
0x000000000044fcbf <+>:mov %rax,(%rsp)
0x000000000044fcc3 <+>:callq 0x447380 <runtime.mcall>
0x000000000044fcc8 <+>:mov 0x10(%rsp),%rax
0x000000000044fccd <+>:inc %rax
0x000000000044fcd0 <+>:cmp $0x3b9aca00,%rax
0x000000000044fcd6 <+>:jl 0x44fcb1 <main.start+>
0x000000000044fcd8 <+>:nop
0x000000000044fcd9 <+>:mov 0x28(%rsp),%rax
0x000000000044fcde <+>:mov %rax,(%rsp)
0x000000000044fce2 <+>:movq $0xffffffffffffffff,0x8(%rsp)
0x000000000044fceb <+>:callq 0x44f8f0 <sync.(*WaitGroup).Add>
0x000000000044fcf0 <+>:mov 0x18(%rsp),%rbp
0x000000000044fcf5 <+>:add $0x20,%rsp
0x000000000044fcf9 <+>:retq
0x000000000044fcfa <+>:callq 0x447550 <runtime.morestack_noctxt>
0x000000000044fcff <+>:jmp 0x44fc90 <main.start>
可以看到当前正在执行的函数是main.start而不是runtime.Gosched,在整个start函数中都找不到Gosched函数的身影,原来它被编译器优化了。程序现在停在了0x000000000044fcb8 <+40>: lea 0x241e1(%rip),%rax 这一指令处,该指令下面的第二条callq指令在调用runtime.mcall,我们首先使用si 2来执行2条汇编指令让程序停在下面这条指令处:
=> 0x000000000044fcc3 <+>: callq 0x447380 <runtime.mcall>
然后使用i r rsp rbp rip记录一下CPU的rsp、rbp和rip寄存器的值备用:
(gdb) i r rsprbprip
rsp 0xc000031fb0 0xc000031fb0
rbp 0xc000031fc8 0xc000031fc8
rip 0x44fcc3 0x44fcc3 <main.start+>
继续看0x000000000044fcc3位置的callq指令,它首先会把紧挨其后的下一条指令的地址0x000000000044fcc8放入g2的栈,然后跳转到mcall函数的第一条指令开始执行。回忆一下第二章我们详细分析过的mcall函数的执行流程,结合现在这个场景,mcall将依次完成下面几件事:
把上面call指令压栈的返回地址0x000000000044fcc8取出来保存在g2的sched.pc字段,把上面我们查看到的rsp(0xc000031fb0)和rbp(0xc000031fc8)分别保存在g2的sched.sp和sched.bp字段,这几个寄存器代表了g2的调度现场信息;
把保存在g0的sched.sp和sched.bp字段中的值分别恢复到CPU的rsp和rbp寄存器,这样完成从g2的栈到g0的栈的切换;
在g0栈执行gosched_m函数(gosched_m函数是runtime.Gosched函数调用mcall时传递给mcall的参数)。
继续看gosched_m函数
runtime/proc.go : 2623
// Gosched continuation on g0.
func gosched_m(gp *g) {
if trace.enabled { //traceback 不关注
traceGoSched()
}
goschedImpl(gp) //我们这个场景:gp = g2
}
gosched_m函数只是简单的在调用goschedImpl:
runtime/proc.go : 2608
func goschedImpl(gp *g) {
......
casgstatus(gp, _Grunning, _Grunnable)
dropg() //设置当前m.curg = nil, gp.m = nil
lock(&sched.lock)
globrunqput(gp) //把gp放入sched的全局运行队列runq
unlock(&sched.lock)
schedule() //进入新一轮调度
}
goschedImpl函数有一个g指针类型的形参,我们这个场景传递给它的实参是g2,goschedImpl函数首先把g2的状态从_Grunning设置为_Grunnable,并通过dropg函数解除当前工作线程m和g2之间的关系(把m.curg设置成nil,把g2.m设置成nil),然后通过调用我们已经分析过的globrunqput函数把g2放入全局运行队列之中。
g2被挂入全局运行队列之后,g2以及其它一些相关部分的状态和关系如下图所示:

从上图我们可以清晰的看到,g2被挂在了sched的全局运行队列里面,该队列有一个head头指针指向队列中的第一个g对象,还有一个tail尾指针指向队列中的最后一个g对象,队列中各个g对象通过g的schedlink指针成员相互链接起在一起;g2的sched结构体成员中保存了调度所需的所有现场信息(比如栈寄存器sp和bp的值,pc指令寄存器的值等等),这样当g2下次被schedule函数调度时,gogo函数会负责把这些信息恢复到CPU的rsp, rbp和rip寄存器中,从而使g2又得以从0x44fcc8地址处开始在g2的栈中执行g2的代码。
把g2挂入全局运行队列之后,goschedImpl函数继续调用schedule()进入下一轮调度循环,至此g2通过自己主动调用Gosched()函数自愿放弃了执行权,达到了调度的目的。
Go语言调度器之主动调度(20)的更多相关文章
- Linux进程核心调度器之主调度器schedule--Linux进程的管理与调度(十九)
主调度器 在内核中的许多地方, 如果要将CPU分配给与当前活动进程不同的另一个进程, 都会直接调用主调度器函数schedule, 从系统调用返回后, 内核也会检查当前进程是否设置了重调度标志TLF_N ...
- Linux核心调度器之周期性调度器scheduler_tick--Linux进程的管理与调度(十八)
我们前面提到linux有两种方法激活调度器:核心调度器和 周期调度器 一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU 另一种是通过周期性的机制, 以固定的频率运行, 不时的检测是否有必要 因 ...
- 深入Golang调度器之GMP模型
前言 随着服务器硬件迭代升级,配置也越来越高.为充分利用服务器资源,并发编程也变的越来越重要.在开始之前,需要了解一下并发(concurrency)和并行(parallesim)的区别. 并发: 逻 ...
- Linux CFS调度器之唤醒抢占--Linux进程的管理与调度(三十)
我们也讲解了CFS的很多进程操作 table th:nth-of-type(1){ width: 20%; } table th:nth-of-type(2){ width: 20% ; } 信息 函 ...
- Linux CFS调度器之task_tick_fair处理周期性调度器--Linux进程的管理与调度(二十九)
1. CFS如何处理周期性调度器 周期性调度器的工作由scheduler_tick函数完成(定义在kernel/sched/core.c, line 2910), 在scheduler_tick中周期 ...
- 工作流调度器之Azkaban
Azkaban 1. 工作流调度器概述 1.1. 为什么需要工作流调度系统 一个完整的数据分析系统通常都是由大量任务单元组成:shell脚本程序,java程序,mapreduce程序.hive脚本等 ...
- 重新梳理调度器——GMP 调度模型
调度器--GMP 调度模型 Goroutine 调度器,它是负责在工作线程上分发准备运行的 goroutines. 首先在讲 GMP 调度模型之前,我们先了解为什么会有这个模型,之前的调度模型是什么样 ...
- Kubernetes之调度器和调度过程
scheduler 当Scheduler通过API server 的watch接口监听到新建Pod副本的信息后,它会检查所有符合该Pod要求的Node列表,开始执行Pod调度逻辑.调度成功后将Pod绑 ...
- Linux调度器 - deadline调度器
一.概述 实时系统是这样的一种计算系统:当事件发生后,它必须在确定的时间范围内做出响应.在实时系统中,产生正确的结果不仅依赖于系统正确的逻辑动作,而且依赖于逻辑动作的时序.换句话说,当系统收到某个请求 ...
随机推荐
- Java技术——Java多线程学习
)适合多个相同程序代码的线程区处理同一资源的情况.比如下面这个买票的例子. //使用Thread实现 public static class MyThread extends Thread{ priv ...
- HDU 4919 Exclusive or 数学
题意: 定义 \[f(n)=\sum\limits_{i=1}^{n-1}(i\oplus (n-i))\] 求\(f(n),n \leq 10^{500}\) 分析: 这个数列对应OEIS的A006 ...
- MySQL之架构与历史(一)
MySQL架构与历史 和其他数据库系统相比,MySQL有点与众不同,它的架构可以在多种不同的场景中应用并发挥好的作用,但同时也会带来一点选择上的困难.MySQL并不完美,却足够灵活,它的灵活性体现在很 ...
- 借助FreeHttp为任意移动端web网页添加vConsole调试
以下介绍在不用修改代码并发布项目的情况下,为我们日常使用的移动web应用(如手机web淘宝)添加vConsole调试工具的方法 vConsole介绍 vConsole是一个轻量.可拓展.针 ...
- Asp.net自定义控件开发任我行(1)-笑傲江湖
1.引言 参加工作5个月了,来到一家小公司,有几只老鸟带我,但不是我公司的,几个礼拜才来一次.来到公司做的第一个项目是web项目,里面有很多的重复代码,页面代码都是千篇一律,你这人也太水了吧,垃圾代码 ...
- 2、HTML基础总结 part-2
1.表单一 <html> <body> <form> 姓名: <input type="text" name="name&quo ...
- 【Remove Duplicates from Sorted Array II】cpp
题目: Follow up for "Remove Duplicates":What if duplicates are allowed at most twice? For ex ...
- MFC深入浅出读书笔记第二部分1
第六章 MFC程序的生死因果 MFC学习过程,这个方法不错,条例清晰. 1.CWinApp -- 取代WinMain地位 WinMain函数的功能由CWinApp的三个函数实现 virtual BO ...
- Python-S9-Day114——Flask开始实战
01 今日内容概要 02 课前分享 03 内容回顾 04 路飞学城表结构(一) 05 路飞学城表结构(二) 06 路飞学城立即支付思路 07 今日作业 08 初识Flask 09 werkzug 10 ...
- C# 调试之 Debug.WriteLine()、Trace.WriteLine()
Trace 类 和 Debug 类的区别在于,Trace 类会同时在 Debug.Release 模式下起作用,而 Debug 只作用在 Debug 模式下. 区别: 1. 输出跟踪信息 Trace. ...