线程那些事儿

线程那些事儿

互联网时代以降,因为在线用户数量的爆炸,单台服务器处理的链接也水涨船高,迫使编程模式由从前的串行模式升级到并发模型,而几十年来,并发模型也是一代代地升级,有IO多路复用、多进程以及多线程,这几种模型都各有长短,现代复杂的高并发架构大可能是几种模型协同使用,不一样场景应用不一样模型,扬长避短,发挥服务器的最大性能,而多线程,由于其轻量和易用,成为并发编程中使用频率最高的并发模型,然后衍生的协程等其余子产品,也都基于它,而咱们今天要分析的 goroutine 也是基于线程,所以,咱们先来聊聊线程的三大模型:java

线程的实现模型主要有3种内核级线程模型、用户级线程模型和两级线程模型(也称混合型线程模型),它们之间最大的差别就在于用户线程与内核调度实体(KSE,Kernel Scheduling Entity)之间的对应关系上。而所谓的内核调度实体 KSE 就是指能够被操做系统内核调度器调度的对象实体,简单来讲, KSE 就是内核级线程,是操做系统内核的最小调度单元,也就是咱们写代码的时候通俗理解上的线程了。python

用户级线程模型

用户线程与内核线程KSE是多对一(N : 1)的映射模型,多个用户线程的通常从属于单个进程而且多线程的调度是由用户本身的线程库来完成,线程的建立、销毁以及多线程之间的协调等操做都是由用户本身的线程库来负责而无须借助系统调用来实现。一个进程中全部建立的线程都只和同一个KSE在运行时动态绑定,也就是说,操做系统只知道用户进程而对其中的线程是无感知的,内核的全部调度都是基于用户进程。许多语言实现的 协程库 基本上都属于这种方式(好比python的gevent)。因为线程调度是在用户层面完成的,也就是相较于内核调度不须要让CPU在用户态和内核态之间切换,这种实现方式相比内核级线程能够作的很轻量级,对系统资源的消耗会小不少,所以能够建立的线程数量与上下文切换所花费的代价也会小得多。但该模型有个原罪:并不能作到真正意义上的并发,假设在某个用户进程上的某个用户线程由于一个阻塞调用(好比I/O阻塞)而被CPU给中断(抢占式调度)了,那么该进程内的全部线程都被阻塞(由于单个用户进程内的线程自调度是没有CPU时钟中断的,从而没有轮转调度),整个进程被挂起。即使是多CPU的机器,也无济于事,由于在用户级线程模型下,一个CPU关联运行的是整个用户进程,进程内的子线程绑定到CPU执行是由用户进程调度的,内部线程对CPU是不可见的,此时能够理解为CPU的调度单位是用户进程。因此不少的协程库会把本身一些阻塞的操做从新封装为彻底的非阻塞形式,而后在之前要阻塞的点上,主动让出本身,并经过某种方式通知或唤醒其余待执行的用户线程在该KSE上运行,从而避免了内核调度器因为KSE阻塞而作上下文切换,这样整个进程也不会被阻塞了。golang

内核级线程模型

用户线程与内核线程KSE是一对一(1 : 1)的映射模型,也就是每个用户线程绑定一个实际的内核线程,而线程的调度则彻底交付给操做系统内核去作,应用程序对线程的建立、终止以及同步都基于内核提供的系统调用来完成,大部分编程语言的线程库(好比Java的java.lang.Thread、C++11的std::thread等等)都是对操做系统的线程(内核级线程)的一层封装,建立出来的每一个线程与一个独立的KSE静态绑定,所以其调度彻底由操做系统内核调度器去作。这种模型的优点和劣势一样明显:优点是实现简单,直接借助操做系统内核的线程以及调度器,因此CPU能够快速切换调度线程,因而多个线程能够同时运行,所以相较于用户级线程模型它真正作到了并行处理;但它的劣势是,因为直接借助了操做系统内核来建立、销毁和以及多个线程之间的上下文切换和调度,所以资源成本大幅上涨,且对性能影响很大。编程

两级线程模型

两级线程模型是博采众长以后的产物,充分吸取前两种线程模型的优势且尽可能规避它们的缺点。在此模型下,用户线程与内核KSE是多对多(N : M)的映射模型:首先,区别于用户级线程模型,两级线程模型中的一个进程能够与多个内核线程KSE关联,因而进程内的多个线程能够绑定不一样的KSE,这点和内核级线程模型类似;其次,又区别于内核级线程模型,它的进程里的全部线程并不与KSE一一绑定,而是能够动态绑定同一个KSE, 当某个KSE由于其绑定的线程的阻塞操做被内核调度出CPU时,其关联的进程中其他用户线程能够从新与其余KSE绑定运行。因此,两级线程模型既不是用户级线程模型那种彻底靠本身调度的也不是内核级线程模型彻底靠操做系统调度的,而是中间态(自身调度与系统调度协同工做),也就是 — 『薛定谔的模型』(误),由于这种模型的高度复杂性,操做系统内核开发者通常不会使用,因此更多时候是做为第三方库的形式出现,而Go语言中的runtime调度器就是采用的这种实现方案,实现了Goroutine与KSE之间的动态关联,不过Go语言的实现更加高级和优雅;该模型为什么被称为两级?即用户调度器实现用户线程到KSE的『调度』,内核调度器实现KSE到CPU上的『调度』。服务器

Goroutine为协程?

Go语言基于并发(并行)编程给出的自家的解决方案。goroutine是什么?一般goroutine会被当作coroutine(协程)的 golang实现,从比较粗浅的层面来看,这种认知也算是合理,但实际上,goroutine并不是传统意义上的协程,如今主流的线程模型分三种:内核级线程模型、用户级线程模型和两级线程模型(也称混合型线程模型),传统的协程库属于用户级线程模型,而goroutine和它的Go Scheduler在底层实现上实际上是属于两级线程模型,所以,有时候为了方便理解能够简单把goroutine类比成协程,但内心必定要有个清晰的认知 — goroutine并不等同于协程。多线程

 

REFERENCE:架构

https://juejin.im/entry/5b2878c7f265da5977596ae2并发

相关文章
相关标签/搜索