💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] > [参考](https://lessisbetter.site/2019/03/10/golang-scheduler-1-history/) ## 协程 (co-routine) - 进程切换需要消耗大量的资源 - 线程切换虽然资源少,但是会有锁和冲突检测 线程分为**内核态线程**和**用户态线程**,**用户态线程需要绑定内核态线程** CPU并不能感知用户态线程的存在,它只知道它在运行1个线程,这个线程实际是内核态线程 用户态线程实际有个名字叫协程(co-routine),为了容易区分,我们使用协程指用户态线程,使用线程指内核态线程 ## 协程和线程有3种映射关系 * N:1,N个协程绑定1个线程,优点就是**协程在用户态线程即完成切换,不会陷入到内核态,这种切换非常的轻量快速**。但也有很大的缺点,1个进程的所有协程都绑定在1个线程上,一是某个程序用不了硬件的多核加速能力,二是一旦某协程阻塞,造成线程阻塞,本进程的其他协程都无法执行了,根本就没有并发的能力了。 * 1:1,1个协程绑定1个线程,这种最容易实现。协程的调度都由CPU完成了,不存在N:1缺点,但有一个缺点是协程的创建、删除和切换的代价都由CPU完成,有点略显昂贵了。 * M:N,M个协程绑定N个线程,是N:1和1:1类型的结合,克服了以上2种模型的缺点,但实现起来最为复杂 ## 协称 (goroutine) - 它非常轻量,一个goroutine只占几KB,可在有限的内存空间内支持大量goroutine - 虽然一个goroutine的栈只占几KB,但实际是可伸缩的,如果需要更多内容,runtime会自动为goroutine分配 ## 老调度器 **调度器的任务是在用户态完成goroutine的调度,而调度器的实现好坏,对并发实际有很大的影响,并且Go的调度器就是M:N类型的,实现起来也是最复杂** 老的调度,新的在2012年被使用 ![](https://lessisbetter.site/images/2019-03-old-scheduler.png) - runtime在Go中很重要,许多程序运行时的工作都由runtime完成 - 调度器就是runtime的一部分,虚线圈出来的为调度器,他分为: 1. M,代表线程,它要运行goroutine 2. Global G Queue,是全局goroutine队列,所有的goroutine都保存在这个队列中,goroutine用G进行代表 M想要执行、放回G都必须访问全局G队列,并且M有多个,即多线程访问同一资源需要加锁进行保证互斥/同步,所以全局G队列是有互斥锁进行保护的 老的调度器有4个缺点: 1. 创建、销毁、调度G都需要每个M获取锁,这就形成了**激烈的锁竞争**。 2. M转移G会造成**延迟和额外的系统负载**。比如当G中包含创建新协程的时候,M创建了G’,为了继续执行G,需要把G’交给M’执行,也造成了**很差的局部性**,因为G’和G是相关的,最好放在M上执行,而不是其他M’。 3. M中的mcache是用来存放小对象的,mcache和栈都和M关联造成了大量的内存开销和差的局部性。 4. 系统调用导致频繁的线程阻塞和取消阻塞操作增加了系统开销。 ## 新调度器 新调度器引入了以下概念: * **P**:**Processor,它包含了运行goroutine的资源**,如果线程想运行goroutine,必须先获取P,P中还包含了可运行的G队列,GOMAXPROCS设置P的数量 * work stealing:当M绑定的P没有可运行的G时,它可以从其他运行的M’那里偷取G。 现在有了最熟悉的调度模型 GMP * **G**: goroutine * **M**: 工作线程 * **P**: 处理器,它包含了运行Go代码的资源,M必须和一个P关联才能运行G ## 调度器 ### 两大思想 - **复用线程**:协程本身就是运行在一组线程之上,不需要频繁的创建、销毁线程,而是对线程的复用。在调度器中复用线程还有2个体现: 1. work stealing,当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程。 2. hand off,当本线程因为G进行系统调用阻塞时,线程释放绑定的P,把P转移给其他空闲的线程执行。 - **利用并行**:GOMAXPROCS设置P的数量,当GOMAXPROCS大于1时,就最多有GOMAXPROCS个线程处于运行状态,这些线程可能分布在多个CPU核上同时运行 ### 两小策略 - **抢占**:在coroutine中要等待一个协程主动让出CPU才执行下一个协程,在Go中,一个goroutine最多占用CPU 10ms,防止其他goroutine被饿死,这就是goroutine不同于coroutine的一个地方 - **全局G队列**:在新的调度器中依然有全局G队列,但功能已经被弱化了,当M执行work stealing从其他P偷不到G时,它可以从全局G队列获取G