文章目录
- 多任务编程:
- 一、进程概述
- 1、定义
- 2、进程在OS中的产生过程
- 3、相关概念
- 4、进程的状态与转换
- 5、进程的特点
- 二、基于fork函数的多进程
- 1、os.fork()函数
- 2、os模块中与进程相关的其他函数
- 3、孤儿进程与僵尸进程
- 三、基于multiprocessing.Process的多进程
- 1、思路
- 2、实现方法
- ①.创建进程对象
- ②.启动进程
- ③.回收进程
- ④.进程对象的其他相关方法/属性
- ⑤.自定义进程类——重写Process类的run()方法
- 四、基于进程池multiprocessing.Pool的多进程
- 1、进程池的必要性
- 2、进程池的原理
- 3、进程池的使用
多任务编程:
- 作用:充分利用计算机多核资源,提高程序的运行效率。
- 实现方案:多进程;多线程
- 相关概念:
- 并发:多个任务在同一时间间隔内发生。表面上看像是多个任务同时进行,实际是任务在时间片上的轮转(即多个任务在内核上以极短的时间快速切换),也就是说,每个时刻只有一个任务占有内核资源。
- 并行:多个任务利用计算机多核资源而同时执行,称这些多个任务间为并行关系
一、进程概述
1、定义
进程(process)是计算机中已运行的程序的实体。
【注】程序是可执行的文件,它静态地占有磁盘空间;而进程是动态地过程,占有计算机运行资源,具有生命周期。
2、进程在OS中的产生过程
3、相关概念
CPU时间片:系统将CPU的执行时间分割为一个个时间段,称为时间片。
PCB(进程控制块):内存中一个专门的数据结构,用于存放进程信息,使参与并发执行的程序能独立运行,也便于系统唯一标识;PCB包含进程描述信息、进程控制和管理信息、资源分配清单 和 处理机相关信息。
PID(进程标识符):系统为每个进程分配的一个大于0的整数,用来唯一地标识进程。
父子进程:系统中每个进程(除系统初始化进程外)都有唯一的父进程,可以有0或多个子进程;父子进程的关系便于进程管理。
4、进程的状态与转换
- 五态
- 就绪态:进程处于准备运行状态,它已获得了除CPU之外的一切资源,等待分配CPU资源。
- 运行态:进程在CPU上运行(某一时刻上占有CPU时间片)。
- 阻塞态:进程因等待某一事件(包括除CPU以外一切所需的资源)而暂停运行,让出CPU;又称为等待态。
- 创建状态:创建一个新的进程,也即,获取除CPU外资源的过程;详细步骤:申请空白PCB,并填充控制和管理进程的信息;系统分配资源。
- 结束状态:进程从系统中消失(进程正常结束或意外中断),所占用的资源得到释放。
5、进程的特点
- 进程可以使用计算机的多核资源
- 进程是计算机分配资源的最小单位
- 进程之间的运行相互独立、互不影响
- 每个进程拥有独立的空间,各自使用自己空间内的资源
Python创建进程常用的有os.fork()函数、multiprocessing模块和Pool进程池
二、基于fork函数的多进程
【注】os.fork()函数只适用于Unix/Linux/Mac系统上运行,在Windows操作系统中不可用。
1、os.fork()函数
pid = os.fork()
功能: 创建新的进程
返回值:整数
负数——进程创建失败返回pid一个负数
正数——进程创建成功返回pid一个正数,表示新进程的PID
创建进程成功后,在新进程中返回0
- 注意:
- 子进程复制父进程的全部内存空间,但从调用fork函数的下一句开始执行
- 父子进程各自独立运行,运行顺序不一定,父子进程对各自内存空间的操作不会相互影响
import os
from time import sleep
pid = os.fork()
if pid < 0:
print("Create process failed")
elif pid == 0:
sleep(5)
print("The new process", os.getpid())
else:
sleep(4)
print("The old process", os.getpid())
print("Fork test over")
2、os模块中与进程相关的其他函数
os.getpid()
返回值:返回当前进程的PIDos.getppid()
返回值:返回当前进程的父进程的PIDos._exit(n)
功能:以状态码n退出进程
注解:退出的标准方法是使用sys.exit([n]),_exit()通常只用在fork()子进程中
3、孤儿进程与僵尸进程
- 孤儿进程:父进程先于子进程退出,此时子进程称为孤儿进程。孤儿进程会被系统进程收养,此时系统进程就会成为孤儿进程新的父进程。若孤儿进程退出,这个新的父进程会自动处理孤儿进程的退出状态。
- 僵尸进程:子进程先于父进程退出,而父进程没有处理子进程的退出状态,此时子进程称为僵尸进程。僵尸进程虽然结束,但是会存留部分PCB在内存中,大量的僵尸进程会浪费系统的内存资源。
- 如何避免僵尸进程的产生
- os模块中使用wait函数处理子进程退出
- 在创建二级子进程的情况下,使用wait函数处理僵尸进程
- 通过signal处理子进程退出
- 原理:子进程退出时会发送信号给父进程,如果父进程忽略子进程信号,那么系统会自动处理子进程的退出
- 方法:调用标准库模块signal中的singal函数,如下:
import signal
signal.signal(singal.SIGCHLD, singal.SIG_IGN)
- 特点:非阻塞函数,不影响父进程进行,可以处理所有子进程退出。
①.案例一:使用wait函数处理子进程退出
pid, status = os.wait()
功能: 在父进程中阻塞,等待处理子进程的退出。
返回值:pid 退出的子进程的PID
status 子进程的退出状态,一个16位数字pid, status = os.waitpid(pid, option)
功能:在父进程中处理子进程的退出状态
参数:pid -1 表示等待任意子进程退出
>0 表示等待指定的子进程退出
返回值:pid 退出的子进程的PID
status 子进程退出状态,一个16位数字
import os
pid = os.fork()
if pid < 0:
print("Error")
elif pid == 0:
print("Child process:", os.getpid())
os._exit(1)
else:
pid, status = os.wait() # 等待处理僵尸进程(必须子进程做完才能执行父进程)
# pid, status = os.waitpid(-1, os.WNOHANG) # 非阻塞状态
print("pid:", pid)
print("status:", os.WEXITSTATUS(status))
②.案例二:在创建二级子进程的情况下处理僵尸进程
"""
(1)父进程创建子进程,等待回收子进程
(2)子进程创建二级子进程然后退出
(3)二级子进程称为孤儿,和原来的父进程一同执行事件
"""
import os
from time import *
def f1():
for i in range(4):
sleep(2)
print("写代码......")
def f2():
for i in range(5):
sleep(5)
print("测代码......")
pid = os.fork() # 父进程创建子进程
if pid < 0:
print("Error")
elif pid == 0:
p = os.fork() # 子进程创建二级子进程
if p == 0:
f2() # 二级子进程执行f2函数
else:
os._exit(0) # 子进程创建二级子进程后立即退出
else:
os.wait() # 父进程等待回收子进程
f1()
③.案例三:通过signal处理子进程退出
"""signal方法避免僵尸进程的产生"""
import os
import time
import signal
signal.signal(signal.SIGCHLD, signal.SIG_IGN) # 子进程发出退出信号后父进程进行忽略
pid = os.fork()
if pid < 0:
print("Error")
elif pid == 0:
print("Child PID:", os.getpid())
else:
while True:
time.sleep(8)
break
三、基于multiprocessing.Process的多进程
multiprocessing模块可在Unix/Linux/Mac/Windows系统上运行
1、思路
使用multiprocessing模块的Process类创建进程的思路如下:
2、实现方法
①.创建进程对象
- multiprocessing模块提供了一个Process类代表一个进程对象,语法如下:
from multiprocessing import Process
p = Process([group, [target, [name, [args, [kwargs, [daemon]]]]]])
参数说明如下
group:参数未使用,保留参数,值始终是None
target:可调用对象,一般绑定要执行的目标函数
name:为当前进程实例起的别名,默认命名格式Process-N
args:表示传递给target函数的参数元组
kwargs:表示传递给target函数的参数字典
daemon:默认None,表示从创建的进程继承;实际用户自定义进程一般为默认值为False
注意:
- 使用multiprocessing模块同样会复制父进程的内存空间,父子进程互不影响
- 子进程只运行target绑定的函数部分
- 惯常的用法,multiprocessing的主进程只用来创建和回收子进程,其他事件交由子进程处理
- multiprocessing创建的子进程无法使用标准输入
②.启动进程
p.start()
功能:启动进程活动,该方法每个进程对象只能用一次;
说明:启动进程从绑定的target函数开始,该函数作为子进程的执行内容,而进程也被真正创建。
③.回收进程
p.join([timeout])
功能:阻塞等待回收进程,或等待多少秒(用来避免僵尸进程的产生)
参数:可选参数timeout表示超时时间;若采用默认值None,则阻塞直到调用join()方法的进程终止。
from multiprocessing import Process
from time import sleep
a = 1
# 子进程执行函数
def fun1():
print("子进程1开始执行")
global a
print("a=", a)
a = 10000
sleep(3)
print("子进程1执行完毕")
def fun2():
sleep(2)
print("子进程2")
if __name__ == "__main__":
funs = [fun1, fun2]
tasks = []
# 创建进程对象
for i in funs:
p = Process(target=i)
tasks.append(p) # 用列表保存进程对象
p.start()
# 启动进程(创建原进程的子进程,子进程执行传参的函数)
sleep(1)
print("父进程干点事")
# 回收进程
for i in tasks:
i.join() # 子进程随父进程退出时没有使用join,则会成为僵尸进程
print("parent a:", a)
④.进程对象的其他相关方法/属性
p.run() 标准 run() 方法将传递给对象构造函数的可调用对象(即target)作为目标参数,若不指定target,那么就会默认执行Process的run方法
p.terminate() 不管任务是否完成,立即终止
p.name 进程的别称
p.pid 进程的PID
p.is_alive 判断进程实例是否还在执行p.daemon 设置父子进程的退出关系
- 初始值继承自创建进程
- 若设置为True,那么子进程会随父进程的退出而结束(换种说法,当进程退出时,它会尝试终止其所有作为守护进程的子进程)
- 要求必须在start()前设置
- daemon=True的效果相当于join(),所以设置daemon=True后不需要使用join()
from multiprocessing import Process
from time import sleep, ctime
def tm(loop):
for i in range(loop):
sleep(2)
print(ctime())
if __name__ == "__main__":
p = Process(target=tm, name="owhyt", args=(3, ))
# 子进程随父进程退出
p.daemon = True
p.start()
print("Name:", p.name) # 获取名称
print("PID:", p.pid) # 获取PID
print("is Alive:", p.is_alive()) # 是否在生命周期
# p.join()
# 子进程随父进程退出时没有使用join,则会成为僵尸进程
⑤.自定义进程类——重写Process类的run()方法
- 创建步骤
- 继承Process类
- 重写__init__方法添加自定义属性,super方法加载父类属性
- 重写run()方法
- 使用方法
- 实例化自定义进程类
- 调用start()方法,会自动执行run()方法
- 调用join()回收进程
from multiprocessing import Process
import time
import os
#继承Process类
class SubProcess(Process):
# 由于Process类本身也有__init__初识化方法,这个子类相当于重写了父类的这个方法
def __init__(self,interval,name='', target=None, args=()):
super(SubProcess, self).__init__() # 调用Process父类的初始化方法
# Process.__init__(self)
self.interval = interval # 接收参数interval
if name: # 判断传递的参数name是否存在
self.name = name # 如果传递参数name,则为子进程创建name属性,否则使用默认属性
#重写了Process类的run()方法
def run(self):
print("子进程(%s) 开始执行,父进程为(%s)"%(os.getpid(),os.getppid()))
t_start = time.time()
time.sleep(self.interval)
t_stop = time.time()
print("子进程(%s)执行结束,耗时%0.2f秒"%(os.getpid(),t_stop-t_start))
def child_1(interval):
print("子进程(%s)开始执行,父进程为(%s)" % (os.getpid(), os.getppid()))
t_start = time.time() # 计时开始
time.sleep(interval) # 程序将会被挂起interval秒
t_end = time.time() # 计时结束
print("子进程(%s)执行时间为'%0.2f'秒"%(os.getpid(),t_end - t_start))
if __name__=="__main__":
print("------父进程开始执行-------")
print("父进程PID:%s" % os.getpid()) # 输出当前程序的ID
p1 = SubProcess(target=child_1, args=(1, ), interval=1, name='owhyt')
p2 = SubProcess(interval=2)
#对一个不包含target属性的Process类执行start()方法,就会运行这个类中的run()方法
p1.start() # 启动进程p1
p2.start() # 启动进程p2,这里会执行p2.run()
# 输出p1和p2进程的执行状态,如果真正进行,返回True,否则返回False
print("p1.is_alive=%s"%p1.is_alive())
print("p2.is_alive=%s"%p2.is_alive())
#输出p1和p2进程的别名和PID
print("p1.name=%s"%p1.name)
print("p1.pid=%s"%p1.pid)
print("p2.name=%s"%p2.name)
print("p2.pid=%s"%p2.pid)
print("------等待子进程-------")
p1.join() # 等待p1进程结束
p2.join() # 等待p2进程结束
print("------父进程执行结束-------")
四、基于进程池multiprocessing.Pool的多进程
multiprocessing模块的Pool类,即Pool进程池,它解决实例化多个Process类(几十上百甚至更多)带来的相关问题。
1、进程池的必要性
- 进程的创建和销毁过程消耗的资源较大
- 尤其在任务众多、任务执行时间短、任务运行密集时,需频繁地创建和销毁进程,容易造成计算机的高负荷
2、进程池的原理
- 进程池创建一定数量的进程来处理事件,事件处理完后,进程不退出继续处理其他事件,直到所有事件全都处理完毕,再统一销毁进程。这样,可以增加进程的重复利用,降低资源消耗。
3、进程池的使用
from multiprocessing import Pool
①.创建进程池对象,放入一定数量的进程
pool = Pool([processes])
功能:创建进程池对象
参数:指定进程数量,若不指定数量,则系统自动调用os.cpu_count()返回的值
②.将事件加入进程池队列执行
pool.apply_async(func, [args, [kwds]]])
功能:以非阻塞方式执行func封装的事件,多个任务在进程池中并发执行
参数:func是事件函数
args 元组类型,给func按位置传参
kwds 字典类型,给func按键值对传参
返回值:AsyncResult对象(可称事件函数对象)
pool.apply(func, [args, [kwds]]]
功能:以阻塞方式执行func封装的事件,func只在进程池中的一个工作进程中执行,必须等待上一个进程退出才能执行下一个进程,换言之,任务是顺序执行。
参数:func是事件函数
注意:该apply方法不建议使用
③.关闭进程池
pool.close()
功能:关闭进程池,使其不再接受新的任务,当所有任务执行完成后,工作进程会退出。
pool.terminate()
功能:立即停止工作进程,不再处理未完成的任务。
④.回收进程池中的进程
pool.join()
功能:主进程阻塞,等待子进程的退出(回收进程池中的进程),必须在close或terminate之后使用
from multiprocessing import Pool
from time import sleep, ctime
# 进程池事件
def worker(msg):
sleep(2)
print(msg)
return ctime()
if __name__ == "__main__":
# 创建进程池
# pool = Pool()
pool = Pool(5)
# 向进程池中添加事件
for i in range(20):
msg = "Hello %d" % i
r = pool.apply_async(func=worker, args=(msg,))
# 关闭进程池(进程池中不再接纳新的事件)
pool.close()
# 回收进程池(阻塞/等待现有的进程池事件执行完毕)
pool.join()
print(r.get()) # 获取最后一个事件函数的返回值