进程、线程、Goroutine
进程是操作系统进行资源调度的基本单位;线程是CPU调度的基本单位,是操作系统可识别的最小执行和调度单位,每个线程完成不同的任务,但是共享同一地址空间;Goroutiune是go实现的一个协程,是一个用户级线程。
进程线程的区别
- 一个线程只能属于一个进程,而一个进程可以有多个线程。
- 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
- 进程是资源分配的最小单位,线程是CPU调度的最小单位。
- 进程切换的开销也远大于线程切换的开销。
线程携程的区别
- 相比线程,其启动的代价很小,以很小栈空间启动。
- 能够动态地伸缩栈的大小,最大可以支持到Gb级别。
- 工作在用户态,切换成很小
线程模型
线程创建、管理、调度等采用的方式称为线程模型。线程模型一般分为以下三种:
- 内核级线程(Kernel Level Thread)模型
- 用户级线程(User Level Thread)模型
- 两级线程模型,也称混合型线程模型
三大线程模型最大差异就在于用户级线程与内核调度实体KSE(KSE,Kernel Scheduling Entity)之间的对应关系。KSE是Kernel Scheduling Entity的缩写,其是可被操作系统内核调度器调度的对象实体,是操作系统内核的最小调度单元,可以简单理解为内核级线程。
GMP线程与模型
Golang在底层实现了混合型线程模型。M即系统线程,由系统调用产生,一个M关联一个KSE,即两级线程模型中的系统线程。G为Groutine,即两级线程模型的的应用及线程。M与G的关系是N:M。
GMP模型
G-M-P分别代表:
- G - Goroutine,Go协程,是参与调度与执行的最小单位
- M - Machine,指的是系统级线程
- P - Processor,指的是逻辑处理器,P关联了的本地可运行G的队列(也称为LRQ),最多可存放256个G。
GMP调度流程
- 线程M要想运行任务,就要先获取P,即M先与P关联
- P从本地队列(local p) 中获取G
- 若本地队列中没有可供运行的G,M会先尝试从全局队列获取一批G放到P的本地队列
- 若全局队列也没有找到可以运行的G,M会随机从其他P的本地队列中偷一半的G放到自己的P本地队列
- 拿到可以运行的G后,M先运行G,G执行以后,M会从P中获取下一个G,一直不断重复
GMP调度策略
- 复用线程: work stealing 和 hand off 机制保证M线程的高效复用
- 利用并行: GOMAXPROCS 设置P的数量,充分利用CPU的多核和m多线程
- 抢占:完全公平的时间片轮转,每个G只能占用10ms,防止其他G饿死
线程和协程映射关系
金山办公的后端面试时被问到了,内核线程和协程总是会有一个映射关系的,既然协程是挂在线程上执行的,为何协程会高效呢?底层还是在线程上运行呀。
此刻我们通过总结上面的知识点就能知道答案。
协程只是内核线程上的一个小任务片段 。go通过 GOMAXPROCS() 确保了创建出足够的M队列和P队列,work stealing 和 hand off 机制保证M线程的高效复用
如下附图一张