为了解决一个程序能够并发处理多个任务,所以在操作系统中引入了多进程、多线程;此处主要讨论多进程,多进程编程分为两种:1、由操作系统内核提供的fork函数完成多进程的创建、销毁等操作;2、由第三方模块(multiprocessing)提供更为专业的多进程操作。此外,还需要注意进程池的应用(多用于频繁的创建、销毁较多进程的情况)。
一、多进程基本概念
1、父子进程
在操作系统中除了初始化进程(PID=1,系统内核启动的第一个用户级进程,是所有其它进程的父进程)之外,每个进程都有一个父进程,可能有0个或者多个子进程。由此形成父子进程关系。我们认为每个进程都是父进程发起请求创建的。
进程信息查看:
进程树: pstree
父进程PID : ps -ajx
2、孤儿、僵尸进程
孤儿进程:父进程先于子进程退出,此时子进程变为孤儿进程
孤儿进程会被系统指定的进程所“收养”,即该进程成为孤儿进程新的父进程。在孤儿进程退出时,“继父”会进行处理不会使其成为僵尸。
僵尸进程:子进程先于父进程退出,但父进程并没有处理子进程的退出状况,子进程就会成为僵尸进程。
僵尸进程会滞留PCB的部分信息在内存中,大量的僵尸进程会消耗系统资源,所以应该尽量避免僵尸进程的产生
3、如何避免僵尸进程的产生
a、让父进程先退出
b、让父进程处理子进程的退出
二、fork函数
import os # 引入系统模块
1、fork函数
pid = os.fork()
功 能:创建一个新的进程
参 数:无
返回值:失败 -1
成功 0 在新的进程(子进程)中返回
正整数 在原有进程(父进程)中的返回新的进程的PID
说明:
* 子进程会复制父进程全部代码段,包括fork前的代码
* 子进程从fork的下一句开始执行
* 父子进程通常会根据fork返回值的差异选择执行不同的代码 (使用if结构)
* 父子进程在执行上互不干扰,执行顺序不确定
* 子进程虽然复制父进程内存空间,但是有自己的特性,比如PID号,PCB,进程栈空间等
* 父子进程空间独立,各自修改各自的内容,互不影响
2、进程相关函数
os.getpid()
功能:获取当前进程的PID号
返回值 : 当前进程PID
os.getppid()
功能 : 获取当前进程父进程的PID号
返回值 : 父进程PID
os._exit(status)
功能 : 结束一个进程
参数 : 表示进程的结束状态是一个整数
sys.exit([status])
功能 : 结束一个进程,抛出异常
参数 : 传入一个正整数表示结束状态
传入字符串表示结束打印
* sys.exit 可以通过捕获SystemExit异常阻止退出
try:
sys.exit("进程退出")
except SystemExit as e:
print(e)
3、fork如何避免僵尸进程的产生
a、让父进程先退出
创建二级子进程
1)父进程创建子进程等待子进程退出
2)子进程创建二级子进程,然后马上退出
3)二级子进程成为孤儿,处理具体事件
b、让父进程处理子进程的退出
I)使用wait或者waitpid函数
pid,status = os.wait()
功能 :在父进程中阻塞等待处理子进程的退出
返回值 : pid 退出的子进程的PID号
status 子进程的退出状态
pid,status = os.waitpid(pid,option)
功能 : 同wait
参数 : pid -1 表示任意子进程退出
>0 整数 指定PID号的子进程退出
option 0 表示阻塞等待
WNOHANG 表示非阻塞
返回值 : 同wait
注:waitpid(-1,0) ====== wait()
II)使用信号处理
#处理僵尸进程
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
三、multiprocessing 标准库模块创建进程
1. 需要将要做的事件封装为函数
2. 使用multiprocessing中提供的Process类创建进程对象
3. 通过进程对象和Process初始化函数对进程进行设置,并且绑定要执行的事件
4. 启动进程,会自动执行相关联函数
5. 事件完成后回收进程
import multiprocessing # 导入标准库模块
1、创建子进程类
p = multiprocessing.Process()
功能:创建子进程
参数:name :给创建的进程起一个名字
默认 process-1
target :目标函数
args :元组 给target指定的函数传递的参数位置
kwargs :字典 给target指定的函数传递的参数键值
返回:由目标函数产生的进程对象
2、进程对象的属性和函数
p.start()
功能 : 启动子进程此时进程真正创建
p.join([timeout])
功能 : 阻塞等待回收相应的子进程
参数 : 默认为阻塞,timeout为超时时间
属性:
p.name 进程名称
p.pid 创建的进程的PID号
p.is_alive() 进程状态
p.daemon
默认值为False 表示主进程结束后不影响子进程的执行
如果设置为True 则主进程执行完毕所有的子进程一同退出
说明:
* 设置必须在 start()前
* 一般使用daemon = True时不用加join
* 该属性并不是 linux/unix系统中所说的守护进程设置
补充:
守护进程 : 生命周期长,随系统创建随系统销毁。
不受前端控制,后台运行
操作系统进程,或者是自动化运行进程居多
3、特点
* multiprocessing创建进程是原来进程的子进程,创建后父子进程各自执行互不影响
* 子进程同样是复制父进程的空间,子进程对内容的修改不会影响父进程空间
* join回收子进程,会有效的阻止僵尸进程产生
4、创建自己的进程类
a、继承Process类
b、重写__init__ 并且调用父类的__init__
c、重写run方法,此时生成对象后调用start就会自动运行run
四、进程池
原因:如果有大量任务需要多进程完成,且可能需要频繁的创建和删除进程,给计算机带来大量的资源消耗。
解决方法:在进程池内运行一定数量进程,通过这些进程完成进程池队列中的事件,直到事件执行完毕,减少进程不断的创建删除过程。
进程池处理事件的流程:
1. 创建进程池,在池内放入适量的进程
2. 将事件加入到进程池等待队列
3. 使用进程池中的进程不断处理事件
4. 所有事件处理后,回收关闭进程池
from multiprocessing import Pool # 导入库
1、进程池相关函数
pool = Pool(processes)
功能 : 创建进程池
参数 : processes指定进程池中进程数量,一般根据计算机内核个数来确定
返回 : pool表示得到进程池对象
pool.apply_async(func,args,kwds)
功能:异步方式将事件放入进程池执行
参数:func 要执行的事件
args 给func用元组传参
kwds 给func用字典传参
返回值:返回一个对象,该对象可以通过get()方法得到func函数的返回值
pool.close()
功能:关闭进程池,使其无法加入新的事件
pool.join()
功能: 阻塞等待进程池退出 (当所有事件处理完毕后)
pool.apply()
用法和apply_async一样,只是需要顺序执行,一个事件结束在执行另一个事件
pool.map(func,iter)
功能:将需要完成的事件放入进程池;类似于内建函数map
参数: func 需要完成的事件函数
iter 可迭代对象给func传参
返回值:func事件函数的返回值列表
r = pool.map(fun,test)
r = []
for i in test:
res = pool.apply_async(fun,(i,))
r.append(res.get())
五、示例
1、fork函数
a、基本功能及变量作用域
import os
from time import sleep
#fork之前的部分只有父进程执行
print("======================")
a = 1
pid = os.fork()
if pid < 0:
print("创建进程失败")
elif pid == 0:
sleep(1)
print("a = ", a)
a = 10000
print("新创建的进程 child a = ", a)
else:
sleep(2)
print("原来的进程 parent a = ", a)
print("程序执行完毕")
b、进程pid
import os
from time import sleep
pid = os.fork()
if pid < 0:
print("Create process failed")
elif pid == 0:
print("Child process")
print("getpid() :",os.getpid()) #子进程自己获取自己的PID
print("getppid() :",os.getppid()) #子进程获取它父进程的PID
print("========================")
else:
sleep(1)
print("Parent process")
print("pid =",pid) #父进程fork返回值就是子进程PID
print("getpid():",os.getpid())#父进程获取自己的PID
c、wait函数
import os,sys
from time import sleep
pid = os.fork()
if pid < 0:
print("create process failed")
elif pid == 0:
sleep(3)
print("子进程PID:",os.getpid())
sys.exit(3)
else:
#等待子进程退出
pid,status = os.wait()
print(pid,status)
print(os.WEXITSTATUS(status)) #获取退出状态
while True:
pass
d、waitpid函数
import os,sys
from time import sleep
pid = os.fork()
if pid < 0:
print("create process failed")
elif pid == 0:
sleep(3)
print("子进程PID:",os.getpid())
sys.exit(3)
else:
#等待子进程退出
while True:
sleep(1)
pid,status = os.waitpid(-1,os.WNOHANG)
print(pid,status)
if os.WEXITSTATUS(status):
break
print("do something others")
while True:
pass
e、二级子进程
#创建二级子进程
import os
from time import sleep
def fun1():
sleep(3)
print("第一件事情")
def fun2():
sleep(4)
print("第二件事情")
# 第一次创建
pid = os.fork()
if pid < 0:
print("Create process error")
elif pid == 0:
# 创建二级进程
pid0 = os.fork()
if pid0 < 0:
print("创建二级进程失败")
elif pid0 == 0:
fun2() #做第二件事
else:
os._exit(0)
else:
os.wait()
fun1() #做第一件事
2、multiprocessing标准库Process函数
a、多进程
from multiprocessing import Process
from time import sleep
import os
def th1():
sleep(3)
print("吃饭")
print(os.getppid(),"----",os.getpid())
def th2():
sleep(2)
print("睡觉")
print(os.getppid(),"----",os.getpid())
def th3():
sleep(4)
print("打豆豆")
print(os.getppid(),"----",os.getpid())
things = [th1,th2,th3]
process = []
for th in things:
p = Process(target = th)
process.append(p) #保存进程对象
p.start()
for p in process:
p.join()
b、多进程传参
from multiprocessing import Process
from time import sleep
def worker(sec,name):
for i in range(3):
sleep(sec)
print("I'm %s"%name)
print("I'm working.....")
#通过args给函数传参
#通过kwargs给函数传参
p = Process(name = "Worker",target = worker,args = (2,),\
kwargs = {'name':'Levi'})
p.start()
#判断进程状态
print("is alive :",p.is_alive())
#进程名
print("process name:",p.name)
#子进程PID
print("process PID:",p.pid)
p.join()
print("=====Process over========")
c、daemon属性
from multiprocessing import Process
from time import sleep,ctime
def tm():
while True:
sleep(2)
print(ctime())
p = Process(target = tm)
#在start前设置daemon为True
p.daemon = True
p.start()
sleep(5)
print("main process over")
3、进程池Pool函数
a、进程池操作
from multiprocessing import Pool
from time import sleep,ctime
#事件函数
def worker(msg):
sleep(2)
print(msg)
return msg + "over"
#创建进程池,放4个进程
pool = Pool(processes = 4)
result = []
for i in range(10):
msg = "hello %d"%i
#将事件放入进程池
r = pool.apply_async(func = worker,args = (msg,))
#保存返回值对象
result.append(r)
# pool.apply(func = worker,args = (msg,))
#关闭进程池
pool.close()
#回收进程池
pool.join()
#获取事件函数的返回值
for i in result:
print(i.get())
b、进程池map函数
from multiprocessing import Pool
import time
def fun(n):
time.sleep(1)
print("执行 pool map事件")
return n * n
#创建进程池
pool = Pool(4)
#使用map将事件放入进程池
r = pool.map(fun,range(6))
print("返回值列表r",r)
pool.close()
pool.join()