6

go语言的并发模型

     无论语言层面的哪种并发模型,到了操作系统层面一定是以线程的形态存在的,操作系统根据资源访问权限的不同,体系架构可以分为用户空间(不可以直接调用系统资源,必须通过系统调用,函数库,shell脚本等调用内核空间的资源)和内核空间(主要访问CPU资源,内存资源,io等硬件资源,为上一层的应用程序提供基础资源)。
    我们现在计算机语言比如Java,Go里面的线程指的都是用户空间里的线程,和操作系统内核空间的线程是不同的,Go语言的并发模型底层是由操作系统提供的线程库来支撑的,所以下面就先从线程模型说起。

线程模型

    线程可以看做进程中的控制流,一个进程至少包含一个线程,因为在进程执行的时候,至少会有一个控制流持续执行。
    所以一个进程的第一个线程会随着一个进程的启动而创建,这个线程经常被叫做主线程。一个进程中可以包含多个线程,这些线程是被已经存在的线程创建出来的。另一方面,线程不可以独立于进程存在,线程的生命周期不可逾越进程的生命周期。
    拥有更多个线程的进程就可以并发的执行多个任务,并且就算某一个任务被阻塞,其他任务也可以正常运行,这样就可以大大改善程序的响应时间和吞吐量。
    线程的实现模型主要有3个,用户级线程模型,内核级线程模型,两级线程模型。他们之间最大的差异就在于线程和内核调度实体(kernel schedule entity KSE)之间的对应关系上。

内核级线程模型

    也叫一对一模型,用户线程和内核线程是一对一的映射关系,如果有一个线程阻塞不会影响其他线程,目前很多线程库(比如Linux, Java, Cpp)都是对操作系统的线程进行一层封装,之后创建出来的线程可以和不同的静态线程互相关联。由于这样的调度动作都是由操作系统完成,所以实现特别简单,直接借助操作系统提供的线程操作能力即可,并且可以在多和操作系统上实现真正的并行,但是这样线程的创建、销毁、多个上下文之间的切换都是由操作系统层面亲自处理,所以消耗的性能代价很大。

用户级线程模型

    也叫做多对一模型,将多个用户级线程映射到一个内核级线程上,这样线程的创建、销毁、多个上下文之间的切换操作都由用户自己实现的线程库负责,对操作系统的内核来说是透明的,一个进程中所有创建出来的线程都和同一个内核级线程动态关联,目前有很多语言实现的协程都是用的这个方法。优点是,相比于内核级线程模型,可以做的很轻量级,极大减少对系统资源的消耗。缺点是,如果在某个用户线程中调用阻塞式系统调用,比如read去读系统IO,这样,一旦内核线程被阻塞线程调度出CPU,所有其他线程都会变成阻塞状态导致整个进程被挂起。(所以,这些语言的协程库往往会采用将阻塞式操作重新封装成非阻塞形式,在之前要阻塞的点上主动让出自己,并且通过某种方式主动唤醒其他线程在当前的内核线程上执行),本质上只利用了一个处理器,不适用于多核处理器,知识解决了并发问题,没有解决并行的问题。

两级线程模型

    也叫做混合线程模型或者多对多线程模型,用户线程和内核线程是多对多的,一个进程中创建了多个KSE,线程可以和不同的内核线程进行动态关联,当某一个线程被调度处CPU的时候同组的其他线程可以和其他的内核线程建立连接,这种动态关联机制 较为复杂,Go的并发就是使用这种方式,为了实现这种模型,Go自己实现了一个运行时调度器(Goroutine Processor Machine 简称GPM)来负责goroutine和内核线程进行动态关联。这样的好处是,可以让大部分线程的上下文切换都发生在用户线程内,多个线程模型又可以充分利用处理器的资源。

参考资源:bilibili


LiberHome
409 声望1.1k 粉丝

有问题 欢迎发邮件 📩 liberhome@163.com