在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
定义于src/runtime/runtime2.go
G-P-M模型示意图:PS:
基本调度过程:
各自携带的信息:
基础知识:普通栈:普通栈指的是需要调度的 goroutine 组成的函数栈,是可增长的栈,因为 goroutine 可以越开越多。 线程栈:线程栈是由需要将 goroutine 放置线程上的 m 们组成,实质上 m 也是由 goroutine 生成的,线程栈大小固定(设置了 m 的数量)。所有调度相关的代码,会先切换到该goroutine的栈中再执行。也就是说线程的栈也是用的g实现,而不是使用的OS的。 全局队列:该队列存储的 G 将被所有的 M 全局共享,为保证数据竞争问题,需加锁处理。 本地队列:该队列存储数据资源相同的任务,每个本地队列都会绑定一个 M ,指定其完成任务,没有数据竞争,无需加锁处理,处理速度远高于全局队列。 上下文切换:对于代码中某个值说,上下文是指这个值所在的局部(全局)作用域对象。相对于进程而言,上下文就是进程执行时的环境,具体来说就是各个变量和数据,包括所有的寄存器变量、进程打开的文件、内存(堆栈)信息等。 线程清理:由于每个P都需要绑定一个 M 进行任务执行,所以当清理线程的时候,只需要将 P 释放(解除绑定)(M就没有任务),即可。P 被释放主要由两种情况:
阻塞是正在运行的线程没有运行结束,暂时让出 CPU。 抢占式调度:在 sysmon会进入一个无限循环, 第一轮回休眠20us, 之后每次休眠时间倍增, 最终每一轮都会休眠10ms. sysmon中有netpool(获取fd事件), retake(抢占), forcegc(按时间强制执行gc), scavenge heap(释放自由列表中多余的项减少内存占用)等处理。 抢占条件:
调用
设置标识,标识该函数可以被中止,当调用栈识别到这个标识时,就知道这是抢占触发的, 这时会再检查一遍是否要抢占。 流程:每创建出一个 g,优先创建一个 p 进行存储,当 p 达到限制后,则加入状态为 waiting 的队列中。 如果 g 执行时需要被阻塞,则会进行上下文切换,系统归还资源后,再返回继续执行。 当一个G长久阻塞在一个M上时,runtime会新建一个M,阻塞G所在的P会把其他的G 挂载在新建的M上。当旧的G阻塞完成或者认为其已经死掉时 回收旧的M(抢占式调度)。 P会对自己管理的goroutine队列做一些调度(比如把占用CPU时间较长的goroutine暂停、运行后续的goroutine等等)当自己的队列消费完了就去全局队列里取,如果全局队列里也消费完了会去其他P的队列里抢任务(所以需要单独存储下一个 g 的地址,而不是从队列里获取)。 总结:
保护现场的抢占式调度和G被阻塞后传递给其他m调用的核心思想,使得goroutine的产生。 从线程调度讲,Go语言相比起其他语言的优势在于OS线程是由OS内核来调度的, ———————————————————————————————————————————————————————————————————————————源码附注:调度流程在M与P绑定后,M会不断从P的Local队列(runq)中取出G(无锁操作),切换到G的堆栈并执行,当P的Local队列中没有G时,再从Global队列中返回一个G(有锁操作,因此实际还会从Global队列批量转移一批G到P Local队列),当Global队列中也没有待运行的G时,则尝试从其它的P窃取(steal)部分G来执行,源代码如下: // go1.9.1 src/runtime/proc.go // 省略了GC检查等其它细节,只保留了主要流程 // g: G结构体定义 // sched: Global队列 // 获取一个待执行的G func findrunnable() (gp *g, inheritTime bool) { // 获取当前的G对象 _g_ := getg() top: // 获取当前P对象 _p_ := _g_.m.p.ptr() // 1. 尝试从P的Local队列中取得G 优先_p_.runnext 然后再从Local队列中取 if gp, inheritTime := runqget(_p_); gp != nil { return gp, inheritTime } // 2. 尝试从Global队列中取得G if sched.runqsize != 0 { lock(&sched.lock) // globrunqget从Global队列中获取G 并转移一批G到_p_的Local队列 gp := globrunqget(_p_, 0) unlock(&sched.lock) if gp != nil { return gp, false } } // 3. 检查netpoll任务 if netpollinited() && sched.lastpoll != 0 { if gp := netpoll(false); gp != nil { // non-blocking // netpoll返回的是G链表,将其它G放回Global队列 injectglist(gp.schedlink.ptr()) casgstatus(gp, _Gwaiting, _Grunnable) if trace.enabled { traceGoUnpark(gp, 0) } return gp, false } } // 4. 尝试从其它P窃取任务 procs := uint32(gomaxprocs) if atomic.Load(&sched.npidle) == procs-1 { goto stop } if !_g_.m.spinning { _g_.m.spinning = true atomic.Xadd(&sched.nmspinning, 1) } for i := 0; i < 4; i++ { // 随机P的遍历顺序 for enum := stealOrder.start(fastrand()); !enum.done(); enum.next() { if sched.gcwaiting != 0 { goto top } stealRunNextG := i > 2 // first look for ready queues with more than 1 g // runqsteal执行实际的steal工作,从目标P的Local队列转移一般的G过来 // stealRunNextG指是否steal目标P的p.runnext G if gp := runqsteal(_p_, allp[enum.position()], stealRunNextG); gp != nil { return gp, false } } } ... } 当无G可执行时,M会与P解绑,进入休眠状态 用户态阻塞/唤醒 当Goroutine因为Channel操作而阻塞(通过gopark)时,对应的G会被放置到某个wait队列(如channel的waitq),该G的状态由 当阻塞的G被G2唤醒(通过goready)时(比如channel可读/写),G会尝试加入G2所在P的runnext,然后再是P Local队列和Global队列。 SYSCALL 当G被阻塞在某个系统调用上时,此时G会阻塞在 当系统调用完成后,G会重新尝试获取一个idle的P,并恢复执行,如果没有idle的P,G将加入到Global队列。
系统调用能被调度的关键有两点: runtime/syscall包中,将系统调用分为SysCall和RawSysCall,前者和后者的区别是前者会在系统调用前后分别调用entersyscall和exitsyscall(位于src/runtime/proc.go),做一些现场保存和恢复操作,这样才能使P安全地与M解绑,并在其它M上继续执行其它G。某些系统调用本身可以确定会长时间阻塞(比如锁),会调用entersyscallblock在发起系统调用前直接让P和M解绑(handoffp)。 另一个是sysmon,它负责检查所有系统调用的执行时间,判断是否需要handoffp。 sysmonsysmon是一个由runtime启动的M,也叫监控线程,它无需P也可以运行,它每20us~10ms唤醒一次,主要执行:
抢占式调度当某个goroutine执行超过10ms,sysmon会向其发起抢占调度请求,由于Go调度不像OS调度那样有时间片的概念,因此实际抢占机制要弱很多: Go中的抢占实际上是为G设置抢占标记(g.stackguard0),当G调用某函数时(更确切说,在通过newstack分配函数栈时),被编译器安插的指令会检查这个标记,并且将当前G以runtime.Goched的方式暂停,并加入到全局队列。 NETPOLLG的获取除了p.runnext,p.runq和sched.runq外,还有一中G从netpoll中获取,netpoll是Go针对网络IO的一种优化,本质上为了避免网络IO陷入系统调用之中,这样使得即便G发起网络I/O操作也不会导致M被阻塞(仅阻塞G),从而不会导致大量M被创建出来。 G创建:G结构体会复用,对可复用的G管理类似于待运行的G管理,也有Local队列(p.gfree)和Global队列(sched.gfree)之分,获取算法差不多,优先从p.gfree中获取(无锁操作),否则从sched.gfree中获取并批量转移一部分(有锁操作),源代码参考src/runtime/proc.go:gfget函数。 从Goroutine的角度来看,通过
G的几种暂停方式:
|
请发表评论