进程、线程、协程的衍生关系及MPG模型

2021-12-14  本文已影响0人  梁帆

协程的英文名称叫做coroutine,在go中,叫goroutine

一、用户栈和内核栈

32位linux系统上,进程的地址空间为4G,包括1G的内核地址空间——内核栈,和3G的用户地址空间——用户栈

内核栈

内核栈,是各个进程在刚开始建立的时候通过内存映射共享的,但是每个进程拥有独立的4G的虚拟内存空间从这一点看又是独立的,互不干扰的(只是刚开始大家都是映射的同一份内存拷贝)。

用户栈

用户栈就是大家所熟悉的内存四区,包括:代码区、全局数据区、堆区、栈区


内存结构图

内核栈和用户栈的切换

当进程在用户空间运行时,CPU堆栈寄存器的内容是用户堆栈地址,使用用户栈。当进程在内核空间时,CPU堆栈寄存器的内容是内核栈地址空间,使用的是内核栈。
当进程因为中断或系统调用进入内核时,进程使用的堆栈也需要从用户栈到内核栈。进程陷入内核态后,先把用户堆栈的地址保存到内核堆栈中,然后设置CPU堆栈寄存器为内核栈的地址,这样就完成了用户栈到内核栈的转换。
当进程从内核态恢复到用户态时,把内核中保存的用户态堆栈的地址恢复到堆栈指针寄存器即可。这样就实现了内核栈到用户栈的转换。

二、进程的栈

内核在创建进程的时候,在创建task_struct的同时,也会为进程创建相应的堆、栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。
当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;
当进程在内核空间时,cpu堆栈指针寄存器里面的内 容是内核栈空间地址,使用内核栈。

三、线程的栈

我们知道,线程之间是共享同一个进程堆空间,但是每个线程的栈空间是独立的。
线程栈的空间开辟在所属进程的堆区与共享内存区之间,线程与其所属的进程共享进程的用户空间,所以线程栈之间可以互访。

四、协程的栈

我们已经知道线程是进程中的执行体,拥有一个执行入口以及从进程虚拟地址空间分配的栈,包括用户栈和内核栈。由线程创建的执行体就是协程,因为用户程序不能操作内存空间,所以只能给协程分配用户栈,而操作系统对协程一无所知。所以协程又被称为“用户态线程”。
操作系统会记录线程控制信息,而线程获得CPU时间片以后才可以执行。
由线程创建的执行体,就是所谓的协程。

四、进程、线程与CPU核心数的关系

这个取决于不同语言的并发模型。如python就是1个内核线程对应一个用户进程。python虽然支持多线程,但是你可以发现cpu使用率无法超过100%,即使你是个八核计算机,它也只用到1个核,如果想在python中用到多核,就得开多进程。
像java和c++这种,就是一个内核线程对应一个用户线程。这样你在java中开了3个线程,理论上最多就能把cpu利用到300%。
像go的花,就是内核线程和用户线程没有固定的对应关系

五、协程vs线程

协程 线程
创建数量 轻松创建上百万个协程而不会导致系统资源衰竭 通常最多不能超过1万个
内存占用 初始分配4k堆栈,随着程序的增长自动增长删除 创建线程时必须制定堆栈且是固定的,通常以M为单位
切换成本 协程切换只需要保存3个寄存器,耗时约200ns 线程的切换需要保存几十个寄存器,耗时约1000ns
调度方式 非抢占式,由goroutine主动交出控制权 在时间片用完后,由CPU中断任务强行将其调度走,这时必须保存很多信息
创建销毁 goroutine因为是由go runtime负责管理的,创建和销毁的消耗比较小,是用户级的 创建和开销巨大,因为要和操作系统打交道,是内核级的,通常解决办法是线程池

五、MPG并发模型

上一篇 下一篇

猜你喜欢

热点阅读