调度器版本演进
单线程调度器
1)该版本调度器在0.X版本实现,代码结构比较简单,是一种G-M模型。
优点:代码简单
缺点:程序只能存在一个活跃线程
2)代码实现
static void scheduler(void) {
G* gp;
lock(&sched);
if(gosave(&m->sched)){
lock(&sched);
gp = m->curg;
switch(gp->status){
case Grunnable:
case Grunning:
gp->status = Grunnable;
gput(gp);
break;
...
}
notewakeup(&gp->stopped);
}
gp = nextgandunlock();
noteclear(&gp->stopped);
gp->status = Grunning;
m->curg = gp;
g = gp;
gogo(&gp->sched);
}
3)流程解读
3.1)获取调度器全局锁:lock(&sched);
3.2)调用runtime.gosave保存栈寄存器和程序计数器
3.3)获取需要运行的goroutine并解锁全局锁:gp = nextgandunloc()
3.4)绑定goroutine到全局线程m
3.5)执行runtime.gogo运行新的goroutine
多线程调度器
1)该调度器在golang1.0版本实现;
优点:支持多线程
缺点:a)调度器和锁是全局资源,调度状态中心化存储,锁竞争问题严重;b)每个线程都需要处理内存缓存,导致大量内存占用,并影响数据局部性,c)系统调用频繁对正在运行的线程阻塞和解除阻塞,增加额外开销。
2)代码实现
static void schedule(G *gp) {
schedlock();
if(gp != nil) {
gp->m = nil;
uint32 v = runtime·xadd(&runtime·sched.atomic, -1<<mcpuShift);
if(atomic_mcpu(v) > maxgomaxprocs)
runtime·throw("negative mcpu in scheduler");
switch(gp->status){
case Grunning:
gp->status = Grunnable;
gput(gp);
break;
case ...:
}
} else {
...
}
gp = nextgandunlock();
gp->status = Grunning;
m->curg = gp;
gp->m = m;
runtime·gogo(&gp->sched, 0);
}
3)流程解读
执行流程与单线程调度相似
任务窃取调度器
1)该版本调度器在1.1版本实现,该版本在原G-M模型的基础上,引入了处理器P,并在处理器P的基础上实现了基于工作窃取的调度机制;
2)代码实现
static void schedule(void) {
G *gp;
top:
if(runtime·gcwaiting) {
gcstopm();
goto top;
}
gp = runqget(m->p);
if(gp == nil)
gp = findrunnable();
...
execute(gp);
}
3)流程解读
3.1)如果当前运行时,在等待垃圾回收,就执行runtime.gcstopm;
3.2)调用runqget从本地获取待执行的goroutine,如果获取不到,则调用runtime.findrunnable触发工作窃取,从其他处理器的队列中或从全局运行队列获取待执行的goroutine;
3.3)调用runtime.execute在当前线程M上执行goroutine
G-M-P模型
协程G
G代表goroutine,在golang表示一个需要执行的任务,协程通过go关键字创建,golang编译器遇到go关键字会替换成runtime.newproc函数调用。
数据结构
type g struct {
stack stack
stackguard0 uintptr
preempt bool // 抢占信号
preemptStop bool // 抢占时将状态修改成 `_Gpreempted`
preemptShrink bool // 在同步安全点收缩栈
m *m //当前 Goroutine 占用的线程,可能为空
sched gobuf// 存储 Goroutine 的调度相关的数据
atomicstatus uint32
goid int64
}
线程M
线程M是操作系统真实的物理线程,调度器做多可以创建10000个线程,最多只有GOMAXPORCS个活跃线程。
数据结构
type m struct {
g0 *g//持有调度栈的goroutine
curg *g//当前线程上运行的用户goroutine
p puintptr
nextp puintptr
oldp puintptr
}
处理器P
处理器P是线程M和协程G的中间层,它提供线程需要的上下文环境,负责调度线程上的等待队列,通过P的调度,每一个线程M可以执行多个协程G。
数据结构
type p struct {
m muintptr // 绑定的线程m
runqhead uint32 // 待执行队列头
runqtail uint32 // 待执行队列尾
runq [256]guintptr // 待执行的goroutine队列
runnext guintptr // 下一个要执行的goroutine
...
}