什么是进程?线程?协程?

本文从操作系统原理出发结合代码实践讲解了以下内容:

什么是进程,线程和协程?
它们之间的关系是什么?
为什么说Python中的多线程是伪多线程?
不同的应用场景该如何选择技术方案?

进程

我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序则是具有某种功能的程序,程序是运行于操作系统之上的。

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。

一、进程一般由三部分组成:

1、程序

程序用于描述进程要完成的功能,是控制进程执行的指令集;

2、数据集合

数据集合是程序在执行时所需要的数据和工作区;

3、进程控制

程序控制块(Program Control Block,简称PCB),包含进程的描述信息和控制信息,是进程存在的唯一标志。

二、进程具有的特征:

  1. 动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
  2. 并发性:任何进程都可以同其他进程一起并发执行;
  3. 独立性:进程是系统进行资源分配和调度的一个独立单位;
  4. 结构性:进程由程序、数据和进程控制块三部分组成。

线程

在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。

后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程序的要求了。于是就发明了线程。

线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。

(读到这里可能有的读者迷糊,感觉这和Java的内存空间模型不太一样,但如果你深入的读过《深入理解Java虚拟机》这本书的话你就会恍然大悟)

如上图,在任务管理器的进程一栏里,WebStorm和PhpStorm就是进程,而在进程下面又有着多个执行不同任务的线程。

协程

协程是一种用户态的轻量级线程,避免了无意义的调度,由此可以提高性能;但同时协程也失去了线程使用多CPU的能力。协程的调度完全由用户控制。从技术的角度来说,“协程就是你可以暂停执行的函数”。协程拥有自己的寄存器上下文和栈。

协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

进程与线程的区别

前面讲了进程与线程,但可能你还觉得迷糊,感觉他们很类似。的确,进程与线程有着千丝万缕的关系,下面就让我们一起来理一理:

1、地址空间

线程是进程内的一个执行单元,进程内至少有一个线程,它们共享进程的地址空间,而进程有自己独立的地址空间。

2、资源拥有

进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源。

3、调度单位

线程是处理器调度的基本单位,但进程不是。进程与线程二者均可并发执行。

4、能否单独执行

每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口,但是线程不能够独立执行,必须依存在进程中,由进程提供多个线程执行控制。

协程与线程的区别

  1. 一个线程可以有多个协程,一个进程也可以单独拥有多个协程;
  2. 线程、进程都是同步机制,而协程则是异步;
  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态;
  4. 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力;
  5. 协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的 CPU 资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程;
  6. 线程是协程的资源。协程通过 Interceptor 来间接使用线程这个资源;
  7. 内存消耗方面(一个线程的内存在 MB 级别,而协程只需要 KB 级别)(重要的)
  8. 线程在创建的时候会返回Id,而协程没有Id要的)
  9. 线程切换需要陷入内核,然后进行上下文切换,而协程在用户态由协程调度器完成,不需要陷入内核,这代价就小了要的)

为什么说Python中的多线程是伪多线程?

python是解释型的语言,在设计之初为了数据安全,采用GIL 的全名是 the Global Interpreter Lock (全局解释锁):

  1. 每个线程需要先获取GIL
  2. 得到GIL的线程执行代码,主动sleep让出cpu或被挂起。
  3. 释放GIL以便下一个线程获取。

熟悉多线程编程的朋友,应该很轻易的理解。

显然在python里,一个进程下永远只能有一个线程拿到GIL,GIL需要反复的释放、获取。因此说“Python的多线程是鸡肋”是有道理的。

但是,就如同单个cpu采用并发的方式一样,能否认它的作用吗?
对于cpu密集型的线程来说,GIL确实影响很大。对于IO密集型,影响很小。

Python开一条线程也确实是开起来了,真实存在的一个线程对象,只是因为Gil这把锁,这个锁会在底层进行自动释放和持有,同时只有一条线程能够持有持有Gil这把锁,这时候,CPU才会在这条线程上面去跑。

不同的应用场景该如何选择技术方案?

进程

需要安全稳定时用进程

线程

等待慢速I/O时,交给一个线程等待,接着做其它的事情

协程

所有异步的情况下,有回调的。例如“Ajax网络请求、即时通讯”都是最常见的。

暂无评论

发送评论 编辑评论


				
上一篇
下一篇