锁定g和m
在执⾏
锁定操作很简单,只需设置
lockedm 会休眠,直到某⼈将 lockedg 交给它。⽽不幸拿到 lockedg 的 M,
则要将
UnlockOSThread可主动解除锁定
系统调用
有两类系统调用:Syscall 和 RawSyscall
RawSyscall对应的系统调用都是非阻塞的,不需要runtime的参与,会立即返回,如果用RawSyscall去调用阻塞的系统调用,p将被浪费。
Syscall 增加了 entersyscall/exitsyscall,确保sysmon正常执行,sysmon会在系统调用长时间阻塞时,调度其他任务。有的系统调用一定会长时间阻塞,这些系统调用会执行entersyscallblock主动交出关联的p。退出系统调用的快速路径 exitsyscallfast 是指能重新绑定原有或空闲的 P,以继续当前 G 任务执⾏。如果多次尝试绑定 P 失败,那么只能走慢速路径,将当前任务放⼊待运⾏队列。
entersyscall -> reentersyscall -> entersyscall_sysmon -> notewakeup
entersyscallblock -> entersyscallblock_handoff -> handoffp(releasep)
exitsyscall -> exitsyscallfast -> exitsyscallfast_pidle
-> exitsyscall0
目前的 Go 的调度器实现中设计了工作线程的自旋(spinning)状态:
- 如果一个工作线程的本地队列、全局运行队列或网络轮询器中均没有可调度的任务,则该线程成为自旋线程;
- 满足该条件、被复始的线程也被称为自旋线程,对于这种线程,运行时不做任何事情。
自旋线程在进行暂止之前,会尝试从任务队列中寻找任务。当发现任务时,则会切换成非自旋状态, 开始执行
当一个
如果最后一个自旋线程发现工作并且停止自旋时,则复始一个新的自旋线程。 这个方法消除了不合理的线程复始峰值,且同时保证最终的最大
总的来说,调度器的方式可以概括为: 如果存在一个空闲的 这个方法消除了不合理的线程复始峰值,且同时保证最终的最大 CPU 并行度利用率。
因此,就绪一个
- 提交一个
- 执行
- 检查
而从自旋到非自旋转换的一般流程为:
- 减少
- 执行
- 在所有
M 的结构
- 持有用于执行调度器的
- 持有用于信号处理的
- 持有线程本地存储
- 持有当前正在运行的
- 持有运行
- 表示自身的自旋和非自旋状态
- 管理在它身上执行的
- 将自己与其他的
- 持有当前线程上进行内存分配的本地缓存
调度器
- 管理了能够将
- 管理了空闲的
- 管理了
- 管理了可被复用的
- 管理了
P,Processor,人为抽象的资源,数量通常等于cpu核数
P 只是处理器的抽象,而非处理器本身,它存在的意义在于实现工作窃取(work stealing)算法。 简单来说,每个 P 持有一个 G 的本地队列。
在没有
当引入了
保存 g 的运行入口 gostartcallfn 将要执行的函数
默认最多能创建10000个m,可用runtime/debug.SetMaxThreads修改这个值
⽤户可调⽤
所有
所有
m0 和 g0
每个m都有一个g0,运行在系统栈上,也叫线程栈或调度栈,g0不是go语句生成的,而是在创建m的时候由runtime生成的,一般用于执行调度、gc、内存管理等,g0不会阻塞,也不存在于任何列表中,栈不会被扫描。
m还有一个gsignal,用于处理信号,系统栈和信号栈不会自动增长
g0 和 gsignal 称为系统g,go语句创建的g称为用户g。
m0 和 m0 的 g0 是静态分配的,它们也叫做runtime.m0 和 runtime.g0,m0 是第一个系统线程,g0在此线程里执行引导程序, m0 与 g0 通过指针互相关联。