多进程

多进程

一、多进程理论

[1]什么是进程

  • 正在进行的一个过程或者说一个任务
  • 而负责执行任务则是CPU

(1)单任务

  • 单核 + 多道,实现多个进程的并发执行
  • CPU可能在一个时间段内要完成多个任务
  • 但某一时刻CPU只能做唯一的一个任务

(2)多任务

  • 多个任务并发执行其实就是CPU因为在进程内的转换很快看起来好像多个任务一起执行
  • 实际上只是CPU在各个任务之间不停的转换,充分利用应用阻塞或者读取数据的时间

[2]进程与程序的区别

  • 程序仅仅只是一堆代码而已

  • 而进程指的是程序的运行过程

  • 示例:小明正在做作业,突然妈妈说:快下雨了!小明快帮妈妈一起收衣服!

    • 小明本来正在做作业(CPU正在执行一个程序)
    • 突然妈妈叫小明去帮忙收衣服(用户启动了另一个程序需要CPU处理)
    • 小明放下作业去帮妈妈收衣服(CPU保存当前进程的状态,转而去执行优先级更高的进程)
    • 帮妈妈收好衣服之后,小明继续把作业完成(CPU从之前进程保存的位置继续执行进程)
  • 需要强调的是:同一个程序执行两次,那也是两个进程,比如打开暴风影音,虽然都是同一个软件,但是一个可以播放西游记,一个可以播放水浒传。

[3]进程的调度问题

  • 要想多个进程交替运行
  • 操作系统必须对这些进程进行调度
  • 这个调度也不是随即进行的,而是需要遵循一定的法则
  • 由此就有了进程的调度算法。

(1)先来先服务算法

  • 先来先服务(FCFS)调度算法是一种最简单的调度算法
  • 应用场景:
    • 该算法既可用于作业调度,也可用于进程调度。
    • FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。
    • 由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。

(2)短工作优先调度算法

  • 短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法
  • 应用场景:
    • 该算法既可用于作业调度,也可用于进程调度。
    • 但其对长作业不利;
    • 不能保证紧迫性作业(进程)被及时处理;
    • 作业的长短只是被估算出来的。

(3)时间片轮转算法

1.理论
  • 时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。
  • 在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片
    • 例如,几十毫秒至几百毫秒。
    • 如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。
    • 同时,进程调度程序又去调度当前就绪队列中的第一个进程。
  • 显然,轮转法只能用来调度分配一些可以抢占的资源。
    • 这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。
    • 但打印机等资源是不可抢占的。
2.适用场景
  • 由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。
  • 在轮转法中,时间片长度的选取非常重要。
    • 首先,时间片长度的选择会直接影响到系统的开销和响应时间。
      • 如果时间片长度过短,则调度程序抢占处理机的次数增多。
      • 这将使进程上下文切换次数也大大增加,从而加重系统开销。
    • 反过来,如果时间片长度选择过长
      • 例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕
      • 则轮转法变成了先来先服务法。
  • 时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。
  • 在轮转法中,加入到就绪队列的进程有3种情况:
    • 一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。
    • 另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。
    • 第三种情况就是新创建进程进入就绪队列。
  • 如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。
    • 例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。
    • 这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。

(4)多级反馈队列

1.理论
  • 前面介绍的各种用作进程调度的算法都有一定的局限性。
    • 如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。
  • 而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。
2.调度算法的实施过程
  • 在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
①为多个就绪队列设置优先级
  • 应设置多个就绪队列,并为各个队列赋予不同的优先级。
  • 第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。
  • 该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。
  • 例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
②新进程等待调用
  • 当一个新进程进入内存后
    • 首先将它放入第一队列的末尾,按FCFS原则排队等待调度。
  • 当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;
    • 如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;
    • 如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。
③按顺序调度队列
  • 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;
    • 仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。
  • 如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列)
    • 则此时新进程将抢占正在运行进程的处理机
    • 即由调度程序把正在运行的进程放回到第i队列的末尾
    • 把处理机分配给新到的高优先权进程。

[4]并行与并发

  • 无论是并行还是并发,在用户看来都是’同时’运行的
    • 不管是进程还是线程,都只是一个任务而已
    • 真是干活的是CPU,CPU来做这些任务
    • 而一个CPU同一时刻只能执行一个任务

(1)并发

  • 是伪并行,即看起来是同时运行。
  • 单个CPU + 多道技术就可以实现并发(并行也属于并发)

(2)并行

  • 同时运行
    • 只有具备多个CPU才能实现并行
①单核
  • 单核下
    • 可以利用多道技术
    • 多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的
②多核
  • 有四个核,六个任务
    • 这样同一时间有四个任务被执行
    • 假设分别被分配给了CPU1,CPU2,CPU3,CPU4,
  • 一旦任务1遇到I/O就被迫中断执行
    • 此时任务5就拿到CPU1的时间片去执行
    • 这就是单核下的多道技术
  • 而一旦任务1的I/O结束了
    • 操作系统会重新调用它(需知进程的调度、分配给哪个CPU运行,由操作系统说了算)
    • 可能被分配给四个CPU中的任意一个去执行

③小结

  • 所有现代计算机经常会在同一时间做很多件事
  • 一个用户的PC(无论是单CPU还是多CPU),都可以同时运行多个任务(一个任务可以理解为一个进程)。
    • 启动一个进程来杀毒(360软件)
    • 启动一个进程来看电影(暴风影音)
    • 启动一个进程来聊天(腾讯QQ)
    • 所有的这些进程都需被管理
  • 于是一个支持多进程的多道程序系统是至关重要的

④多道技术概念回顾

  • 内存中同时存入多道(多个)程序
  • CPU从一个进程快速切换到另外一个
  • 使每个进程各自运行几十或几百毫秒
  • 这样,虽然在某一个瞬间
  • 一个CPU只能执行一个任务
  • 但在1秒内,CPU却可以运行多个进程
  • 这就给人产生了并行的错觉,即伪并发
  • 以此来区分多处理器操作系统的真正硬件并行(多个CPU共享同一个物理内存)

(3)总结

  • 并行肯定算并发
  • 单核的计算机肯定不能实现并行,但是可以实现并发。

[5]同步/异步;阻塞/非阻塞

(1)同步

  • 所谓同步
    • 就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。
    • 按照这个定义,其实绝大多数函数都是同步调用。
    • 但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。

(2)异步

  • 异步的概念和同步相对。
    • 当一个异步功能调用发出后,调用者不能立刻得到结果。
    • 当该异步功能完成后,通过状态、通知或回调来通知调用者。
      • 如果异步功能用状态来通知
        • 那么调用者就需要每隔一定时间检查一次
        • 效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一 种很严重的错误)。
      • 如果是使用通知的方式
        • 效率则很高
        • 因为异步功能几乎不需要做额外的操作。
    • 至于回调函数,其实和通知没太多区别。

(3)阻塞

  • 阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。
    • 函数只有在得到结果之后才会将阻塞的线程激活。
    • 有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。
    • 对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
  • 示例:
    • 同步调用
      • apply一个累计1亿次的任务,该调用会一直等待,直到任务返回结果为止,但并未阻塞住(即便是被抢走CPU的执行权限,那也是处于就绪态)
    • 阻塞调用
      • 当socket工作在阻塞模式的时候,如果没有数据的情况下调用recv函数,则当前线程就会被挂起,直到有数据为止

(4)非阻塞

  • 非阻塞和阻塞的概念相对应
    • 指在不能立刻得到结果之前也会立刻返回
    • 同时该函数不会阻塞当前线程。

(5)小结

①同步/异步
  • 同步与异步针对的是函数/任务的调用方式
  • 同步就是当一个进程发起一个函数(任务)调用的时候
    • 一直等到函数(任务)完成,而进程继续处于激活状态。
  • 而异步情况下是当一个进程发起一个函数(任务)调用的时候
    • 不会等函数返回,而是继续往下执行当,函数返回的时候通过状态、通知、事件等方式通知进程任务完成。
②阻塞/非阻塞
  • 阻塞与非阻塞针对的是进程或线程
  • 阻塞是当请求不能满足的时候就将进程挂起
  • 而非阻塞则不会阻塞当前进程

[6]进程的创建

(1)引入

  • 但凡是硬件,都需要有操作系统去管理
    • 只要有操作系统,就有进程的概念
    • 就需要有创建进程的方式
    • 一些操作系统只为一个应用程序设计
      • 比如微波炉中的控制器
      • 一旦启动微波炉
      • 所有的进程都已经存在。
  • 而对于通用系统(跑很多应用程序)
    • 需要有系统运行过程中创建或撤销进程的能力
    • 主要分为4种形式创建新的进程

(2)通过系统创建新进程的四种方式

①系统初始化
  • 查看进程linux中用ps命令,windows中用任务管理器
  • 前台进程负责与用户交互,后台运行的进程与用户无关
  • 运行在后台并且只在需要时才唤醒的进程,称为守护进程
    • 如电子邮件、web页面、新闻、打印
②进程中开启子进程
  • 一个进程在运行过程中开启了子进程
    • nginx开启多进程
    • os.fork
    • subprocess.Popen
    • ...
③交互式请求
  • 用户的交互式请求
  • 创建一个新进程(如用户双击暴风影音)
④批处理作业的初始化
  • 一个批处理作业的初始化(只在大型机的批处理系统中应用)

(3)不同系统的新进程的创建方式

  • 无论哪一种
    • 新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的
①UNIX
  • 在UNIX中该系统调用是:fork;fork会创建一个与父进程一模一样的副本
  • 二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
②Windows
  • 在windows中该系统调用是:CreateProcessCreateProcess既处理进程的创建,也负责把正确的程序装入新进程。

(4)不同系统创建的子进程的异同

①相同点
  • 进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离
  • 任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。
②不同点
  • 在UNIX中,子进程的初始地址空间是父进程的一个副本

提示:子进程和父进程是可以有只读的共享内存区的。

但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。

[7]进程的终止

(1)正常退出(资源)

  • 如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出
  • 在 Linux 中用 exit,在 Windows 中用 ExitProcess

(2)出错退出(自愿)

  • 在程序执行过程中发生错误,导致程序退出。
  • python a.py中a.py不存在)

(3)严重错误(非自愿)

  • 执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常
  • try…except…

(4)被其他进程杀死(非自愿)

  • 如使用命令 kill -9 在 Linux 中强制终止进程。

[8]进程的层次结构

  • 无论UNIX还是windows
    • 进程只有一个父进程
  • 不同的是:
    • 在UNIX中所有的进程
      • 都是以init进程为根,组成树形结构。
      • 父子进程共同组成一个进程组
      • 这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。
    • 在windows中,没有进程层次的概念
      • 所有的进程都是地位相同的
      • 唯一类似于进程层次的暗示
      • 是在创建进程时
      • 父进程得到一个特别的令牌(称为句柄
      • 该句柄可以用来控制子进程
      • 但是父进程有权把该句柄传给其他子进程
      • 这样就没有层次了。

[9]进程的状态

(1)什么是进程的状态

  • 进程状态反映进程执行过程的变化。
  • 这些状态随着进程的执行和外界条件的变化而转换。

(2)三态模型

  • 在三态模型中,进程状态分为三个基本状态,
    • 即运行态
    • 就绪态
    • 阻塞态
  • 执行程序tail,开启一个子进程
  • 执行程序grep,开启另外一个子进程
  • 两个进程之间基于管道’|’通讯,将tail的结果作为grep的输入。
  • 进程grep在等待输入(即I/O)时的状态称为阻塞,此时grep命令都无法运行
  • 其实在两种情况下会导致一个进程在逻辑上不能运行,
  • 进程挂起是自身原因,遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作
  • 与进程无关,是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU。
  • 因而一个进程有三种状态

(3)五态模型

  • 在五态模型中,进程分为
    • 新建态
    • 终止态
    • 运行态
    • 就绪态
    • 阻塞态

[10]进程并发的实现

  • 进程并发的实现在于,硬件中断一个正在运行的进程
    • 把此时进程运行的所有状态保存下来
    • 为此,操作系统维护一张表格,即进程表(process table)
      • 每个进程占用一个进程表项(这些表项也称为进程控制块)

img

  • 该表存放了进程状态的重要信息
    • 程序计数器、堆栈指针、内存分配状况、所有打开文件的状态、帐号和调度信息,以及其他在进程由运行态转为就绪态或阻塞态时,必须保存的信息,从而保证该进程在再次启动时,就像从未被中断过一样。

二、多进程操作

[1]multiprossing模块介绍

  • python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。
    • Python提供了multiprocessing。
    • multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。
  • multiprocessing模块的功能众多:
    • 支持子进程、通信和共享数据、执行不同形式的同步
    • 提供了Process、Queue、Pipe、Lock等组件。
  • 需要再次强调的一点是:
    • 与线程不同,进程没有任何共享状
    • 进程修改的数据,改动仅限于该进程内。

[2]Process类的介绍

(1)创建进程类

  • 语法
Process([group [, target [, name [, args [, kwargs]]]]])
  • 由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:

  1. 需要使用关键字的方式来指定参数
  2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

(2)参数介绍

  • group参数未使用,值始终为None
  • target表示调用对象,即子进程要执行的任务
  • args表示调用对象的位置参数元组,args=(1,2,'ly',)
  • kwargs表示调用对象的字典,kwargs={'name':'ly','age':18}
  • name为子进程的名称

(3)方法介绍

  • p.start()
    • 启动进程,并调用该子进程中的p.run()
  • p.run():
    • 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
  • p.terminate():
    • 强制终止进程p,不会进行任何清理操作
    • 如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。
    • 如果p还保存了一个锁那么也将不会被释放,进而导致死锁
  • p.is_alive():
    • 如果p仍然运行,返回True
  • p.join([timeout]):
    • 主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。
    • timeout是可选的超时时间
    • 需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程

(4)属性介绍

  • p.daemon
    • 默认值为False
    • 如果设为True,代表p为后台运行的守护进程
    • 当p的父进程终止时,p也随之终止
    • 并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
  • p.name:
    • 进程的名称
  • p.pid
    • 进程的pid
  • p.exitcode:
    • 进程在运行时为None;如果为–N,表示被信号N结束(了解即可)
  • p.authkey:
    • 进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。
    • 这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

[3]Process类的使用

  • 注意:在windows中Process()必须放到 if __name == 'main__':

(1)创建并开去子进程的两种方式

①方法一:直接使用Process方法
import time
from multiprocessing import Process


# 定义任务函数
def run(name):
    print(f'进程 {name} 开始!')
    time.sleep(1)
    print(f'进程 {name} 结束!')


if __name__ == '__main__':
    #通过for循环创建对象,并传入参数
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        # 开启进程
        p.start()

"""
进程 1 开始!
进程 3 开始!
进程 2 开始!
进程 4 开始!
进程 1 结束!
进程 3 结束!
进程 2 结束!
进程 4 结束!
"""

②方法二:继承Process类
import time
from multiprocessing import Process


class MyProcess(Process):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print(f'进程 {self.name} 开始!')
        time.sleep(1)
        print(f'进程 {self.name} 结束!')


if __name__ == '__main__':
    for i in range(1, 5):
        p = MyProcess(name=str(i))
        p.start()

"""
进程 1 开始!
进程 2 开始!
进程 3 开始!
进程 4 开始!
进程 1 结束!
进程 3 结束!
进程 4 结束!
进程 2 结束!
"""
③小结
  • 创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去
  • 一个进程对应在内存中就是一块独立的空间
  • 多个进程对应在内存中就是多块独立的内存空间
  • 进程与进程之间数据默认情况下是无法直接交互的,如果想交互可以借助第三方工具或模块

(2)进程之间的内存空间是隔离的

  • 每一个子进程之间的数据是相互隔离的
  • 在执行子进程代码时,只修改自己子进程内的数据,不会影响到其他的子进程
from multiprocessing import Process

money = 0


def run(name):
    global money
    if name == 'Xanadu':
        money = 10000
    else:
        money = 1000
    print(f'{name}{money}钱')


if __name__ == '__main__':
    print(f'现在有{money}钱')
    p1 = Process(target=run, args=('Xanadu',))
    p2 = Process(target=run, args=('bridge',))
    p1.start()
    p2.start()

"""
现在有0钱
Xanadu有10000钱
bridge有1000钱
"""

(3)socket通信变成并发的形式

  • 服务端
import socket
from socket import SOL_SOCKET, SO_REUSEADDR
from multiprocessing import Process

server = socket.socket()

IP = '127.0.0.1'
PORT = 8086

server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

server.bind((IP, PORT))

server.listen()


def run(conn, addr):
    msg_from_client = conn.recv(1024)
    msg_from_client = msg_from_client.decode('utf-8')
    print(f'这是来自{addr}的信息》》》{msg_from_client}')
    msg_send_to_client = msg_from_client.upper()
    msg_send_to_client = msg_send_to_client.encode('utf-8')
    conn.send(msg_send_to_client)


def mian_system():
    while True:
        conn, addr = server.accept()
        p = Process(target=run, args=(conn, addr))
        p.start()


if __name__ == '__main__':
    mian_system()
  • 客户端
import socket

client = socket.socket()

IP = '127.0.0.1'
PORT = 8086

client.connect((IP, PORT))

msg_send_to = input('请输入小写字母:')

msg_send_to = msg_send_to.encode('utf-8')

client.send(msg_send_to)

msg_from_server = client.recv(1024)

msg_from_server = msg_from_server.decode('utf-8')

print(f'这是大写字母:{msg_from_server}')

(4)Process对象的join方法

①非join方法——并行
from multiprocessing import Process
import time


def run(number):
    print(f'子进程 {number} 开始')
    time.sleep(1)
    print(f'子进程 {number} 结束')


def main_fist():
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        p.start()


def main_second():
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        p.start()
        p.join()


def main_third():
    p_list = []
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        p.start()
        p_list.append(p)

    for p in p_list:
        p.join()


if __name__ == '__main__':
    main_fist()
    """
    子进程 1 开始
    子进程 2 开始
    子进程 3 开始
    子进程 4 开始
    子进程 1 结束
    子进程 3 结束
    子进程 2 结束
    子进程 4 结束
    """
②join方法——串行
from multiprocessing import Process
import time


def run(number):
    print(f'子进程 {number} 开始')
    time.sleep(1)
    print(f'子进程 {number} 结束')


def main_fist():
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        p.start()


def main_second():
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        p.start()
        p.join()


def main_third():
    p_list = []
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        p.start()
        p_list.append(p)

    for p in p_list:
        p.join()


if __name__ == '__main__':
    main_second()
    """
    子进程 1 开始
    子进程 1 结束
    子进程 2 开始
    子进程 2 结束
    子进程 3 开始
    子进程 3 结束
    子进程 4 开始
    子进程 4 结束

    """
③join方法——并行
from multiprocessing import Process
import time


def run(number):
    print(f'子进程 {number} 开始')
    time.sleep(1)
    print(f'子进程 {number} 结束')


def main_fist():
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        p.start()


def main_second():
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        p.start()
        p.join()


def main_third():
    p_list = []
    for i in range(1, 5):
        p = Process(target=run, args=(i,))
        p.start()
        p_list.append(p)

    for p in p_list:
        p.join()


if __name__ == '__main__':
    main_third()
    """
    子进程 1 开始
    子进程 2 开始
    子进程 3 开始
    子进程 4 开始
    子进程 2 结束
    子进程 1 结束
    子进程 4 结束
    子进程 3 结束
    """

[4]Process对象的其他方法与属性

(1)计算机上查看当前进程号

  • 什么是进程号
    • 一台计算机上面运行着很多进程,那么计算机是如何区分并管理这些进程服务端呢?
      • 计算机会给每一个运行的进程分配一个PID号
  • 查看进程号
    • Windows系统
      • CMD 命令行 tasklist 即可查看
    • Mac系统
      • 终端运行 ps aux 即可查看
  • (3)如何根据指定进程号查看进程
    • Mac系统
      • 终端运行 ps aux|grep PID 即可查看
    • Windows系统
      • CMD 命令行 tasklist |findstr PID 即可查看
①查看当前进程号current_process().pid 方法
from multiprocessing import Process, current_process


def run():
    print(f'当前进程号{current_process().pid}')


if __name__ == '__main__':
    p = Process(target=run)
    p.start()
    print(f'主程序进程号{current_process().pid}')
    # 主程序进程号31468
    # 当前进程号32180
②查看当前进程的进程号os.getpid() 方法
from multiprocessing import Process, current_process
import os


def run():
    print(f'当前进程号 {os.getpid()}')


if __name__ == '__main__':
    p = Process(target=run)
    p.start()
    print(f'主程序的进程号 {os.getpid()}')
    # 主程序的进程号 21564
    # 当前进程号 42680
③查看当前进程的父进程的进程号os.getppid() 方法
from multiprocessing import Process, current_process
import os


def run():
    print(f'当前进程的父进程进程号: {os.getppid()}')


if __name__ == '__main__':
    p = Process(target=run)
    p.start()
    print(f'主进程的父进程进程号: {os.getppid()}')
    # 主进程的父进程进程号: 51696
    # 当前进程的父进程进程号: 53908

(2)杀死当前进程p.terminate()

  • 告诉操作系统帮我去杀死当前进程
  • 但是需要一定的时间。
  • 代码的运行速度极快

(3)判断当前进程是否存活p.is_alive()

from multiprocessing import Process
import time


def run():
    print('子进程开始')
    time.sleep(2)
    print('子进程结束')


if __name__ == '__main__':
    print('父进程开始')
    p = Process(target=run)
    p.start()

    # 杀死进程需要给操作系统缓冲时间
    p.terminate()
    time.sleep(2)

    print(p.is_alive())
    print('父进程结束')

# 因为将子进程杀死了所以没有执行run函数里的代码
# 父进程开始
# 当前子程序被杀死所以返回False
# False
# 父进程结束
posted @   桃源氏  阅读(205)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示