首先搞清楚几个概念
线程实现模型主要有三种:
KSE是内核调度实体,又称内核级线程,就是可以被内核调度器调度的对象
用户级线程模型:此模型下的线程由用户级别的线程库管理,线程库在进程的用户空间中。包含多个用户级线程的进程
只与一个KSE对应,又被称为M:1线程模型
优点:线程切换不需要让CPU从用户态切换到内核态
缺点:多线程不能被真正的并发执行
内核级线程模型:此模型下的线程是由内核负责管理的,进程中的每一个线程都与一个KSE对应(确定关联),又被称 为1:1线程模型
优点:即使一个线程受到阻塞线程,其他线程也不会受到影响
缺点:内核线程管理成本高很多,线程切换需要进行用户太-内核态-用户态转变
两级线程模型:(同时包含内核级和用户级线程管理)一个进程可以与多个KES相连,又称为M:N线程模型,与内核级线程 模型类似,但不相同的是进程中的用户级线程与KSE并不一一对应,在go语言中不受内核管理的独立控 制流(两级线程模型的中的用户级线程管理)就叫Goroutine
协程:又称为轻量级线程。携程的特点是单线程的。协程不是函数调用。最大的优势就是协程极高的执行效率。因为
子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数
量越多,协程的性能优势就越明显。第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同
时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
再来看Goroutine,Goroutine采用两级线程模型。
go语言主要实现模型
M:代表一个内核级线程
P:代表一个M所需的上下文
G:就是我们所写的go一段并发代码
P中包含一个可与运行G队列
G需要一个M+P才可以执行
M数量一般与P(GOMAXPROCS)的数量大致一样,当M运行的G系统调用(例如io操作)时,这个M会与它所关联的P分开,从而多出一个空闲P,如果P队列中拥有未运行的G,体统会找一个空闲M或新建一个M来执行G
M代表一个内核级线程,本质是一个用户级线程,与一个内核调度实体相关联,由内核控制单线程M的并发
M为单线程协程实现,用户级调度控制我们Go的并发代码
从总体上说,内核始终相连M(除特殊情况),在M内实现协程,即由我们决定什么时候调度G去执行,而由内核决定什么时候去调度M(分配CPU)
最终实现在一个程序(进程)中,一个并发代码G可以被关联到多个内核调度实体,既利用了多核,又利用了协程减少了线程切换的代价