从Linux体系到GPM

Updated on with 0 views and 0 comments

1.Linux-CPU

我们先看一下 Linux 系统的整体结构,这里插入搜集的两张图

image.png

从图上可知,Linux 组成分为 User Space,Kernel Space,Hardware Space
用户层,内核层,硬件层

用户层: 比如我们看到的命令行界面,我们的浏览器,执行的 Shell,或者 gcc 编译器,这些能够直接看到,使用的称之为用户层,个人理解,就是软件层。

内核层:由 Linux 内核实现的 进程管理,内存管理,虚拟化,网络管理,文件管理,等等等一个操作系统基本必备的功能,组合起来变成了内核。

硬件层:CPU,内存,网卡,声卡,IO 设备等等等

再来看一下内核

image.png

内核调用错综复杂,我们只看 threads 一列,
用户层的线程由内核中的调度器来进行调度,最后走到硬件层的CPU,去争抢CPU的时间片

时间片:举个不恰当的例子,在单核计算机中我们如何做到同时做两件事儿了,就涉及到,两个进程争抢时间片或者说 CPU 分配时间片给两个进程,
浏览器拿到时间片就渲染一会儿,网易云拿到时间片就加载一会儿音乐,但是 CPU 调度切换时间非常快,以至于你的感受就是一边在看网页一边听歌,谁有时间片,谁就可以继续执行。

image.png

Linux 内核有调度器:就是调度进程运行,哪个可以跑,哪个阻塞了啥的
时间片:进程拿到时间片可以执行

2.进程/线程

image.png

从任务管理器中看到,这些都是正在跑的进程,有的是有用户界面的,我们可以叫做 前台进程,有的进程是系统的基本服务,后台静默跑的,可以叫做后台进程。

一个应用程序至少有一个进程和一个主线程 然后通过调度器拿时间片来跑自己的程序,当一个程序结束时,这个程序的进程和线程就会消失。

线程我们拿 Word 来举例子

image.png

我们在当前 Word 中一边打字,一边被提示拼写错误。
那么 当前 word 是一个进程,因为我们打开了这个软件并持续运行着。
这个进程中至少有两个线程,一个负责渲染文档让我们书写,另一个线程一直检查我们的拼写,这两个线程之间还有通信,拼写线程发现拼写错误,给渲染线程提示,让渲染线程出红线提示我们拼写错了。

一个程序,至少有一个进程和一个主线程

3.用户线程和内核线程

https://zhuanlan.zhihu.com/p/26279675

线程的实现可以分两类:用户级线程,内核级线程和混合式线程。

用户级线程是指不需要内核支持而在用户程序中实现的线程,它的内核的切换是由用户态程序自己控制内核的切换,不需要内核的干涉。但是它不能像内核级线程一样更好的运用多核 CPU。

优点:

(1) 线程的调度不需要内核直接参与,控制简单。

(2) 可以在不支持线程的操作系统中实现。

(3) 同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起,可以节约更多的系统资源。

缺点:

(1) 一个用户级线程的阻塞将会引起整个进程的阻塞。

(2) 用户级线程不能利用系统的多重处理,仅有一个用户级线程可以被执行。

内核级线程:切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态。可以很好的运用多核 CPU,就像 Windows 电脑的四核八线程,双核四线程一样。

优点:

(1)当有多个处理机时,一个进程的多个线程可以同时执行。

(2) 由于内核级线程只有很小的数据结构和堆栈,切换速度快,当然它本身也可以用多线程技术实现,提高系统的运行速率。

缺点:

(1) 线程在用户态的运行,而线程的调度和管理在内核实现,在控制权从一个线程传送到另一个线程需要用户态到内核态再到用户态的模式切换,比较占用系统资源。(就是必须要受到内核的监控)

关联性

(1) 它们之间的差别在于性能。

(2) 内核支持线程是 OS 内核可感知的,而用户级线程是 OS 内核不可感知的。

(3) 用户级线程的创建、撤消和调度不需要 OS 内核的支持。

(4) 用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。

(5) 在只有用户级线程的系统内,CPU 调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU 调度则以线程为单位,由 OS 的线程调度程序负责线程的调度。

(6) 用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。

4.用户线程和内核线程模型

用户线程和内核线程一对一,真正并行,耗费内核资源

image.png

用户线程和内核线程多对一,一个内核线程阻塞,整个用户线程挂起

image.png

用户线程和内核线程多对多,又并行又省资源,但是需要手动实现

image.png

Go 实现的多线程就是第三种模型,Go 自己实现了一个运行时调度器,来负责 goroutine 和内核线程进行动态关联,将用户线程和内核线程变成多对多模式,Java 是第一种,所以相对来说 go 处理速度比 Java 快

这里 Go 实现的第三种模型我们称之为 GPM

5.GPM 和 go routine

G(Goroutine) :我们所说的协程,为用户级的轻量级线程,每个 Goroutine 对象中的 sched 保存着其上下文信息

M(Machine) :对内核级线程的封装,数量对应真实的 CPU 数(真正干活的对象)

P(Processor) :即为 G 和 M 的调度对象,用来调度 G 和 M 之间的关联关系,其数量可通过 GOMAXPROCS()来设置,默认为核心数

image.png

当前有两个 M,每个 M 都有一个 Processor 来调度 goroutine 的执行,单个 M 中同时只有一个 goroutine 被调度执行,然后 Processor 在调度 goroutine queue 中的下一个来执行。如果当前两个 M 挂接的是同一个内核线程,那么就是并发。如果是不同的内核线程,那么就是并行。

image.png

如果当前 M 中发生系统调用,IO/网络或其他原因被阻塞,Processor 将挂接到 M1(如果内核线程有空闲,创建 M1)然后继续执行剩下的 go routine

image.png

如果当前 M 的 Processor 没有挂接的 go routine 可以被执行了,那么将会偷取另一个上下文一半的 go routine 来执行

`GPM 都是 go runtime 包中的,也就是 go 的运行时系统包含了(内存分配,并发调度器,垃圾收集容器等)相当于 Java 的 jvm


标题:从Linux体系到GPM
作者:ferried
地址:https://blog.eiyouhe.com/articles/2020/03/05/1583418412118.html