Python网络与并发编程 07 线程与进程

进程相关

程序和进程

程序是一堆躺在硬盘中的代码,是“死”的

进程是程序被调用执行时活动的状态,是“活”的

当程序运行时必定会产生很多的数据,那么这些数据会被保存在进程中,所以:

  • 进程是最小的资源单元

注意一点,程序只有在运行状态时才会产生进程,而不运行时就是一堆代码而已。

因此,你可以理解为每一段程序运行时都会创建一个“小房子”,这个“小房子”中存放了很多资源。

*

进程是由谁创建的

进程必定是由操作系统所创建,每个程序运行时都必然会产生一个进程,当然也可以产生多个进程。

如下图所示:

*

进程间的数据交互

不同进程之间的数据一般来说不允许共享,因为每个进程都是独立的“小房子”,每个“小房子”的资源都是自己独享的。

但我们之前学过socket,这玩意儿最早就是用来解决进程间数据交互问题的。

所以,进程之间虽然默认不支持数据交互,但是我们可以使用某些特殊手段让两个进程之间支持数据交互,这样做需要付出一些代价。

进程中的切换

一个CPU核心同一时刻最多只能运行一个进程,而多个CPU核心同一时刻可以运行多个进程,这个就是并发的体现。

*

一个系统中肯定有大量的进程,所以CPU需要不断的进行进程切换:

*

进程切换实际上是由操作系统说了算,但是进程切换的消耗是非常大的,系统需要保留当前进程的状态,进行切换后还需要将进程状态进行恢复,下面介绍一些最基本的进程切换策略。

1)先来先服务策略:

谁先创建进程,CPU就先执行谁。

该算法对一个存活时间很短的进程是相当不利的,如果一个存活时间很长的进程占用了一个CPU核心,而恰巧这个CPU又是单核的,那么其他存活时间短的进程永远也得不到CPU的眷顾了。

所以操作系统的进程调度如果仅有这一种策略是行不通的。

2)短作业优先调度策略:

那个进程作业时间短,CPU就先执行谁。

显然,单一的这种算法会让长作业进程得不到CPU眷顾,故也不能一直采取这种策略。

3)时间片轮询策略:

什么意思呢?就是说假如有多个进程,我每个进程让你运行个三五秒就切换到另一个进程运行,如此来回切换就是时间片轮转,即将时间分段,每个进程只运行一段时间。

在类Unix系统中,我们可以为某个进程分配更多的时间片。

4)多级反馈队列:

这个其实是基于时间片轮转做的,它会将当前所有的活动进程送入一个队列中,根据存活时间来为其分配到不同的队列中,进程存活时间越久,其得到CPU眷顾的次数越低。

如图所示:

*

其实在Unix系统中,我们可以为一个进程分配更多的时间片与更高的优先级,这里不再举例。

专业性解释

进程就是一个程序在一个数据集上的一次动态执行过程。

进程一般由以下三部分组成:

  • 程序
  • 数据集
  • 进程控制块

我们编写的程序用来描述进程要完成哪些功能以及如何完成;

数据集则是程序在执行过程中所需要使用的资源;

进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。

数据集提供所有程序运行时需要的资源,进程控制块用来记录程序的状态,比如说挂起被切换状态还是运行状态等等

线程相关

线程和进程

每个进程创建时也会默认至少创建一个线程,如果把进程比喻为“小房子”,那么线程就是小房子中的人,是真正干活的单元。

因此:

  • 线程是最小的执行单元
  • 一个进程中的所有线程都会共享该进程的所有资源。

需要注意,线程一定是包含在进程内的。

必须先有进程后才能有线程,就像线程这个人必须住在进程的房子里一样:

*

线程是由谁创建的

线程有2种,一般所说的线程都是系统级别创建的,但也有用户级别创建的线程,这种线程常常被称之为纤程。

它们的区别在于:

  • 系统级线程的切换消耗较大
  • 用户级线程的切换消耗较小

所以,对任务执行效率来说,使用 纤程 > 线程。

线程间的数据交互

线程必须存在于进程中,我们上面说过一个进程可以有多个线程,那么想当然的该进程里的所有资源都可以被位于该进程中的线程所拿到。

而跨进程之间的线程数据交互就是属于进程间的数据交互了:

*

线程之间的通信一定要注意线程安全问题,举个例子,假如你和你的同学均是一个线程:

你和你的同学在一个房间中,房间里有一颗糖,趁着你干活的时候你同学将糖吃掉了,你干完活回来之后肯定会以为糖果还在,当你去拿糖果的时候由于糖果不在了所以就会抛出异常。

那么这个就是线程安全问题,如何解决线程安全问题这是之后要聊到的一个话题。

线程的切换

线程切换与进程切换如出一辙,看上面的就行了。

专业性解释

1)一个程序至少有一个进程,一个进程至少有一个线程

2)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

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

4)进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单元。而线程是进程的一个实体,是CPU调度和分派的基本单元,它是比进程更小的能独立运行的基本单元。

5)线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

6)一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。

切换流程

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种切换是由操作系统来完成的。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。

从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:

1、 保存处理机上下文,包括程序计数器和其他寄存器;

2、 更新PCB信息;

3、 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列;

4、 选择另一个进程执行,并更新其PCB;

5、 更新内存管理的数据结构;

6、 恢复处理机上下文;

总而言之,进程的切换非常的消耗资源。

不同的进程之间能进行切换那么不同的线程之间也必定能进行切换,既然线程是最小的执行单元那么同一进程中的线程切换的代价必然是少于进程间的切换的。

程序计数器

我们都知道软件的数据是存储在硬盘上的,这个调用的过程十分缓慢,但是在内存中就会快很多。

同时,一个线程或者进程的切换挂起状态如果是存放在内存中那么是肯定不行的,这个速度对于切换毫秒级别的线程或者进程来说速度依旧不够快。

所以在CPU旁边有了一个程序计数器的存在,由于距离CPU比较近传输状态的时间也会相应缩短。

它的大小并不是很大只有小小的1 - 2kb,主要功能就是存储了这些进程或者线程切换状态的数据。