协程是后起之秀?那么进程和线程呢?

进程、线程和协程概念性的东西这里就不会讲很多,一句话概括:进程、线程和协程实际上都是为并发而生。

但是它们的内存模型是不一样的,下面我们来分析一下各自的特点和关系。

进程及内存模型

进程,是程序的基本执行实体,即可执行程序运行中形成一个独立的内存体,这个内存体有自己独立的地址空间,有独立的堆空间。操作系统会以进程为单位,分配系统资源(CPU 时间片、内存等资源),也就是说进程是系统资源分配的最小单位,如下图所示:

进程特性

  • 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的
  • 并发性:任何进程都可以同其他进程一起并发执行
  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
  • 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
  • 结构特征:进程由程序、数据和进程控制块三部分组成。

多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果。

进程状态转换如下所示:

线程及内存模型

线程,有时被称为轻量级进程,是操作系统调度(CPU 调度)执行的最小单位,如下图所示。

线程特性

在多线程 OS 中,通常是在一个进程中包括多个线程,每个线程都是作为利用 CPU 的基本单位,是花费最小开销的实体。线程具有以下属性。

  • 轻型实体:线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源。线程的实体包括程序、数据和 TCB(线程控制块)
  • 独立调度和分派的基本单位:在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位
  • 可并发执行:在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行
  • 共享进程资源:在同一进程中的各个线程,都可以共享该进程所拥有的资源

Linux 最小执行单元 CPU

对于 Linux 来讲,它不区分你是进程还是线程的,他们都是一个单独的执行单位,CPU 均是通过调度分配时间片来执行,但是对于多核来说是有一定优势的。

扫描二维码关注公众号,回复: 14422453 查看本文章

由上图可知,如果一个进程想更大程度的与其他进程抢占 CPU 的资源,那么可以通过开多个线程的方式来达到。

可以看到进程 A 默认开了 1 个线程,对于内核来讲,它只有 1 个执行单元,进程 B 开了 3 个线程,那么在内核中,该进程就占有 3 个执行单元。

CPU 在内核空间的,它不区分是进程和还是线程,而是通过时间片轮询的方式平均调度分配。因此,进程 B 开了 3 个单元就相对占了比较多的资源。

线程上下文切换

由上述可知,在一定条件下,线程越多,进程利用或者说抢占的 cpu 资源就会越高,如下图所示:

那么是不是线程可以无限制的开启呢?

答案当然不是的,我们知道,当我们 cpu 在内核态切换一个执行单元的时候,会有一个上下文切换的时间成本和性能开销,如下图:

其中性能开销至少有以下两个方面:

  • 切换内核上下文栈
  • 保存寄存器中的状态内容

因此,我们不能大量的开辟线程。很多编程语言在设计上就想了办法,既然我们不能优化 cpu 切换线程的开销,那么我们是否可以在用户态优化该流程呢?

很显然,我们是没权限修改操作系统内核机制的,那么只能在用户态再来一个伪执行单元,那么就是协程了。

协程及切换成本

协程切换比线程切换快主要有两点:

(1)协程切换完全在用户空间,而进行线程切换涉及特权模式切换,需要在内核空间完成;

(2)协程切换相比线程切换做的事情更少,线程需要有内核和用户态的切换,涉及系统调用过程。

协程切换成本

协程切换比较简单,就是把当前协程的 CPU 寄存器状态保存起来,然后将需要切换进来的协程的 CPU 寄存器状态加载的 CPU 寄存器上就 ok 了。而且完全在用户态进行,一般来说一次协程上下文切换最多就是几十 ns 这个量级。

线程切换成本

系统内核调度的对象是线程,因为线程是调度的基本单元(进程是资源拥有的基本单元,进程的切换需要做的事情更多,这里占时不讨论进程切换),而线程的调度只有拥有最高权限的内核空间才可以完成,所以线程的切换涉及到用户空间和内核空间的切换,也就是特权模式切换。

进程一般占用 1 ~ 4g 不等的内存,G 量级。 线程一般占用 2 ~ 8M 不等内存,MB 量级。 而协程占用 2 ~ 4KB 内存,KB 量级。

那么,go 的协程切换成本如此小,占用也那么小,是否可以无限开辟呢? 前面有一篇文章讲到过这个问题,不妨回顾下:Golang无限开启Goroutine?该如何限定Goroutine数量?

总结

总的来说,进程、线程和协程切换都需要性能开销,只是协程开销可以忽略,而且它是用户态的,可以有用户程序来控制,优势比较明显,尤其是Go语言的协程设计。

猜你喜欢

转载自juejin.im/post/7126373438532354055
今日推荐