multiprocessing 模块创建进程
进程创建方法
- 流程特点
【1】 将需要子进程执行的事件封装为函数
【2】 通过模块的Process类创建进程对象,关联函数
【3】 可以通过进程对象设置进程信息及属性
【4】 通过进程对象调用start启动进程
【5】 通过进程对象调用join回收进程(防止僵尸进程出现) - 基本接口使用
Process()
功能 : 创建进程对象
参数 : target 绑定要执行的目标函数 (函数名)
rgs 元组,用于给target函数位置传参
kwargs 字典,给target函数键值传参
p.start()
功能 : 启动进程
注意:启动进程此时target绑定函数开始执行,该函数作为子进程执行内容,此时进程真正被创建
p.join([timeout])
功能:阻塞等待回收进程
参数:超时时间(可选参数)
重要代码:
单个子进程(无参数):
import multiprocessing as mp
from time import sleep
# 子进程函数
def fun():
print("子进程开始")
sleep(3)
print("子进程结束")
# 创建进程对象
p = mp.Process(target=fun)#注意是函数名
# 启动进程
p.start()
# 父进程同时执行
sleep(2)
print('父进程干点事儿')
# 回收进程
p.join()
单个子进程(有参数):
from multiprocessing import Process
from time import sleep
# 子进程函数
def fun(name, age):
sleep(2)
print("I'm %s" % name)
print("I'm %d years old" % age)
# 创建进程对象(两种方法传参)
# p = Process(target=fun, args=('xc', 25)) # 元组
p = Process(target=fun, kwargs={'name': 'xc', 'age': 25}) # 字典
# 启动进程
p.start()
# 回收进程
p.join()
多个子进程:
import multiprocessing as mp
from time import sleep
import os
# 子进程函数1
def fun1():
sleep(2)
print("eat")
print(os.getppid(), '----', os.getpid())
# 子进程函数2
def fun2():
sleep(3)
print("sleep")
print(os.getppid(), '----', os.getpid())
# 子进程函数3
def fun3():
sleep(4)
print("doudou")
print(os.getppid(), '----', os.getpid())
things = [fun1, fun2, fun3]
jobs = []
for th in things:
p = mp.Process(target=th)
jobs.append(p) # 保存进程对象
p.start()
for i in jobs:
i.join()
注意:
- 使用multiprocessing创建进程同样是子进程复制父进程空间代码段,父子进程运行互不影响。
- 子进程只运行target绑定的函数部分,其余内容均是父进程执行内容。
- multiprocessing中父进程往往只用来创建子进程回收子进程,具体事件由子进程完成。
- multiprocessing创建的子进程中无法使用标准输入。
- 进程对象属性
-
p.name 进程名称
-
p.pid 对应子进程的PID号
-
p.is_alive() 查看子进程是否在生命周期
-
p.daemon 设置父子进程的退出关系
- 如果设置为True则子进程会随父进程的退出而结束
- 要求必须在start()前设置
- 如果daemon设置成True 通常就不会使用 join(),因为join()含义是子进程退出(回收)时父进程不退出,但是daemon是父进程退出时子进程退出,矛盾
from multiprocessing import Process
from time import sleep,ctime
def tm():
for i in range(3):
sleep(2)
print(ctime())
p = Process(target = tm,name = 'Tedu')
# p.daemon = True # 子进程会随父进程退出
p.start()
print("Name:",p.name) # 名称
print("PID:",p.pid) # PID
print("Is alive:",p.is_alive()) # 生命周期
进程池实现
-
必要性
【1】 进程的创建和销毁过程消耗的资源较多
【2】 当任务量众多,每个任务在很短时间内完成时,需要频繁的创建和销毁进程。此时对计算机压力较大
【3】 进程池技术很好的解决了以上问题。 -
原理
创建一定数量的进程来处理事件,事件处理完进程不退出而是继续处理其他事件,直到所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗。
- 进程池实现
【1】 创建进程池对象,放入适当的进程
from multiprocessing import Pool
Pool(processes)
功能: 创建进程池对象
参数: 指定进程数量,默认根据系统自动判定(一般不写)
【2】 将事件加入进程池队列执行
pool.apply_async(func,args,kwds)
功能: 使用进程池执行 func事件
参数: func 事件函数
args 元组 给func按位置传参
kwds 字典 给func按照键值传参
返回值: 返回函数事件对象
【3】 关闭进程池
pool.close()
功能: 关闭进程池,之后不能加入新的事件
【4】 回收进程池中进程
pool.join()
功能: 回收进程池中进程,堵塞等待现有的进程执行完毕
示例代码:
from multiprocessing import Pool
from time import sleep, ctime
def worker(msg):
sleep(2)
print(ctime(), '---', msg)
return ctime()
# 创建进程池
pool = Pool()#进程池中可以进行计算机核数量个进程(系统默认)
# pool = Pool(6)#进程池中可以同时进行6个进程
# 向进程池添加事件
for i in range(20):
msg = "Hello %d" % i
r=pool.apply_async(func=worker, args=(msg,))#元组只有一个值的话要加,
# 关闭进程池
pool.close()
# 回收进程池
pool.join()
# 获取事件返回值,函数没有返回值的话返回None
print(r.get())# 因为r会覆盖,所以这个地方输出的是最后一个r对应的函数返回值
进程间通信(IPC)
- 必要性:进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。
- 常用进程间通信方法:管道、消息队列、共享内存、信号、信号量、套接字
管道通信(Pipe)
- 通信原理
在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现通信 - 实现方法
from multiprocessing import Pipe
fd1,fd2 = Pipe(duplex = True)
功能:创建管道
参数:默认True表示双向管道(管道两侧都可读可写)
如果为False 表示单向管道(只能一侧读,另一侧写)
返回值:表示管道两端从的读写对象
如果是双向管道均可读写
如果是单向管道fd1只读 fd2只写
fd.recv()
功能:从管道获取内容(堵塞函数)
返回值:获取到的数据
fd.send(data)
功能:向管道写入内容
参数:要写入的数据
实现代码:
from multiprocessing import Process, Pipe
import os, time
# 创建管道对象
fd1, fd2 = Pipe()
def fun(name):
time.sleep(3)
# 向管道写入内容
fd1.send({name:os.getpid()})
jobs = []
for i in range(5):
p = Process(target=fun, args=(i,))
jobs.append(p)
p.start()
for i in range(5):
# 读取管道内容
data = fd2.recv()
print(data)
for i in jobs:
i.join()
消息队列
- 通信原理:在内存中建立通信模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
- 实现方法:
from multiprocessing import Queue
q = Queue(maxsize=0)
功能:创建队列队列
参数:最多存放的消息个数
返回值:队列对象
q.put(data,[block,timeout])
功能:向队列存入消息
参数:data 要存入的内容
block 设置是否阻塞 ,默认True阻塞(消息队列满时),False为非阻塞
timeout 超时检测
q.get([block,timeout])
功能:从队列取出消息
参数:data 要存入的内容
block 设置是否阻塞 ,默认True阻塞(消息队列为空),False为非阻塞
timeout 超时检测
返回值:返回获取到的内容
q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 获取队列的消息个数
q.close() 关闭队列
实现代码:
from multiprocessing import Queue, Process
from time import sleep
from random import randint
# 创建消息队列
q = Queue(3)
def request():
for i in range(20):
x = randint(0, 100)
y = randint(0, 100)
q.put((x, y))
def handle():
while True:
sleep(0.5)
try:
x, y = q.get(timeout=3)
except:
break
else:
print("%d + %d = %d" % (x, y, x + y))
p1 = Process(target=request)
p2 = Process(target=handle)
p1.start()
p2.start()
p1.join()
p2.join()
共享内存
- 通信原理:在内存中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前的内容。
- 实现方法
from multiprocessing import Value,Array
obj = Value(ctype,data)
功能:开辟共享内存
参数:ctype 表示共享内存空间类型 'i'整型 'f'浮点 'c'字符(只能指定一个类型)
data 共享内存空间初始数据
返回值:共享内存对象
obj.value 对该属性的修改或查看即对共享内存的读写
实现代码(存入一个值):
from multiprocessing import Process, Value
import time
import random
# 创建共享内存
money = Value('i', 5000)#存一个值
# 操作共享内存
def man():
for i in range(30):
time.sleep(0.2)
money.value += random.randint(1, 1000)
def girl():
for i in range(30):
time.sleep(0.16)
money.value -= random.randint(100, 900)
m = Process(target=man)
g = Process(target=girl)
m.start()
g.start()
m.join()
g.join()
print("一月余额:", money.value)
obj = Array(ctype,data)
功能:开辟共享内存空间
参数:ctype 表示共享内存数据类型
data 整数则表示开辟空间的大小(存几个整数的列表),其他数据类型表示开辟空间存放的初始化数据
返回值:共享内存对象
Array共享内存读写:通过遍历obj可以得到的每个值,直接可以通过索引号修改任意值。
* 可以使用obj.value直接打印共享内存中的字节串
实现代码(存入多个值):
from multiprocessing import Process, Array
# shm = Array('i', 5) # 初始化5个整型列表空间
shm = Array('i', [1, 2, 3]) # 初始化列表为[1,2,3]
# shm = Array('c',b'hello') # 字节串
def fun():
# 共享内存对象可迭代
for i in shm:
print(i)
shm[1] = 1000 # 修改共享内存
p = Process(target=fun)
p.start()
p.join()
for i in shm:
print(i, end=" ") # [1, 1000, 3]
# 通过value属性访问字节串(只能字节串使用)
print(shm.value)
本地套接字
- 功能:用于本地两个程序之间进行数据的收发
- 套接字文件:用于本地套接字之间通信时,进行数据传输的介质。
- 创建本地套接字流程
【1】创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
【2】绑定本地套接字文件
sockfd.bind(file)
【3】监听,接收客户端连接,消息收发
listen()–>accept()–>recv(),send()
unix_recv(接收端):
from socket import *
import os
# 确定套接字文件
sock_file = './sock'
# 判断文件是否存在,存在就删除
if os.path.exists(sock_file):
os.remove(sock_file)
# 创建本地套接字
sockfd = socket(AF_UNIX, SOCK_STREAM)
# 绑定套接字文件
sockfd.bind(sock_file)
# 监听,连接
sockfd.listen(3)
while True:
c, addr = sockfd.accept()
while True:
data = c.recv(1024)
if not data:
break
print(data.decode())
c.close()
sockfd.close()
unix_send(发送端):
from socket import *
# 确保两边使用同一个套接字文件
sock_file = './sock'
sockfd = socket(AF_UNIX, SOCK_STREAM)
sockfd.connect(sock_file)
while True:
msg = input(">>")
if not msg:
break
sockfd.send(msg.encode())
sockfd.close()
信号量(信号灯集)
- 通信原理:给定一个数量对多个进程可见,多个进程都可以操作该数量增减,并根据数量值决定自己的行为。
- 实现方法:
from multiprocessing import Semaphore
sem = Semaphore(num)
功能 : 创建信号量对象
参数 : 信号量的初始值
返回值 : 信号量对象
sem.acquire() 将信号量减1 当信号量为0时阻塞
sem.release() 将信号量加1
sem.get_value() 获取信号量数量
实现代码:
from multiprocessing import Semaphore, Process
from time import sleep
import os
# 创建信号量
# 服务程序最多允许三个进程同时执行事件
sem = Semaphore(3)
def handle():
print("%d 想执行事件" % os.getpid())
# 想执行必须获取信号量
sem.acquire() # 减少信号量
print("%d 开始执行操作" % os.getpid())
sleep(3)
print("%d 完成操作" % os.getpid())
sem.release() # 增加信号量
jobs = []
# 5个进程请求执行事件
for i in range(5):
p = Process(target=handle)
jobs.append(p)
p.start()
for i in jobs:
i.join()
print(sem.get_value()) # 获取信号量的值(3)
作业:
multiprocess创建两个进程,同时复制一个文件的上下两半部分,各自复制到一个新的文件里。
from multiprocessing import Process
import os
filename = './timg.jpeg'
# 获取图片大小
size = os.path.getsize(filename)
# 复制文件上半部分
def top():
f = open(filename, 'rb')
n = size // 2
fw = open('top.jpeg', 'wb')
fw.write(f.read(n))
f.close()
fw.close()
# 复制文件下半部分
def bot():
f = open(filename, 'rb')
fw = open('bot.jpeg', 'wb')
f.seek(size // 2, 0)
while True:
data = f.read(1024)
if not data:
break
fw.write(data)
f.close()
fw.close()
t = Process(target=top)
b = Process(target=bot)
t.start()
b.start()
t.join()
b.join()
注意:如果父进程中打开文件,创建进程通信对象或者创建套接字,子进程从父进程内存空间获取这些内容,那么父子进程对该对象的操作会有一定的属性关联。