作者:qyc_3830179 | 来源:互联网 | 2023-10-11 11:54
一、GO"调度器"的由来
二、GO的调度模型:
可以抽象出三个实体:M(thread)、P(processor)、G(goroutine)。
G:GO运行时对goroutine的描述,G中存放并发执行的代码入口地址、上下文、运行环境(关联的P和M)、运行栈等执行相关的信息。G的新建、休眠、恢复、停止都受到Go运行时的管理。
GO运行时的监控线程会监控G的调度,G不会长久地阻塞系统线程,运行时的调度器会自动切换到其他G上运行。G新建或恢复时会添加到运行队列,等待M取出并运行。
M:OS内核线程,是操作系统层面调度和执行的实体。M仅负责执行,M不停地被唤醒或创建。然后执行。
P:代表M和G所需要的资源,是对资源的一种抽象管理,P不是一个段代码实体,而是一个管理的数据结构,P主要是降低M对G的复杂性,增加一个间接的控制层数据结构。P控制GO代码的并行度,它不是实体。
P持有G的队列,P可以隔离调度,解除P和M的绑定就解除了M对一串G的调用。
G并不是执行体,而是存放并发执行体的元信息,包括并发执行的入口函数、堆栈、上下文等信息。G由于保存的是元信息,为了减少对象的分配和回收,G对象是可以复用,只需要将相关元信息初始化为新值即可。
M仅负责执行,M启动时进入运行时的管理代码,这段管理代码必须拿到P后,才能执行调度。
P的数目默认是CPU核心的数量。M和P的数目差不多,但运行时会根据当前的状态动态地创建M,M有一个最大值上限:10000;G与P是M:N的关系,M可以成千上万,远远大于N.
GMP模型设计思想:
三、调度器的设计策略
四、Work Stealing算法的基本原理:
M和P构成一个运行时环境,每个P有一个本地的可调度的G队列,队列里面的G会被M依次调度执行,如果本地队列空了,则去全局队列偷取一部分G,如果全局队列也是空的,则去其他的P中偷取一部分G。
五、什么时候创建M、P、G?
在程序启动过程中会初始化空闲P列表,P是这个时候创建的,同时第一个G也是在初始化过程中被创建的。
每个并发调用都会初始化一个新的G任务,如何唤醒M执行任务。这个唤醒不是特定唤醒某个线程去工作,而是先尝试获取当前线程M,如果无法获取,则从全局调度的空闲M列表中获取可用的M,如果没有可用的M,则新建M,然后绑定P和GY运行。M和P不是一一对应的,而是按需分配的。
M线程有管理调度和切换堆栈的逻辑,但是M必须拿到P后才能运行,可用看到M是自驱动的,单需要P的配合。
六、goroutine经历的过程
Go协程阻塞太长时间会发生什么?
协程的切换时间片是10ms,也就是说 goroutine 最多执行10ms就会被 M 切换到下一个 G。这个过程,又被称为 中断,挂起。
Go协程遇到异步系统调用是怎么做的?
假设M的任务队列里有G1、G2,G1遇到异步系统调用时,M会先执行G2,G1异步返回时,插入回M的任务队列尾部。
Go协程遇到同步系统调用是怎么做的?
假设M的任务队列里有G1、G2,G1遇到同步系统调用时,G1会被调度到另外一个完全空闲的M上执行(如果不存在,则先创建一个新的M),G1同步返回时,插入回M的任务队列尾部。