调度器
最后更新于:2022-04-02 02:35:28
[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
';