进程Process
什么是进程
进程(Process):拥有自己独立的堆和栈,既不共享堆,也不共享栈,进程由操作系统调度;进程切换需要的资源很最大,效率低。
对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。
进程的优缺点
进程的优点:
- 可以使用计算机多核,进行任务的并行执行,提高执行效率
- 运行不受其他进程影响,创建方便
- 空间独立,数据安全
进程的缺点:
- 进程的创建和删除消耗的系统资源较多
进程的创建方式(方法模式)
Python的标准库提供了个模块:multiprocessing
进程的创建可以通过分为两种方式:
1. 方法包装
2. 类包装
创建进程后,使用start()启动进程
【示例】方法模式创建进程
from multiprocessing import Process
import os
from time import sleep
def func1(name):
print("当前进程ID:",os.getpid()) #getpid意思是获取进程ID(get process id)
print("父进程ID:",os.getppid()) #getppid意思是获取父进程ID(get parent process id)
print(f"Process:{name} start")
sleep(3)
print(f"Process:{name} end")
'''
这是一个关于windows上多进程实现的bug。
如果不加__main__限制的话,就会无限递归创建子进程,进而报错。
在windows上,子进程会自动import启动它的这个文件,而在import的时候是会自动执行这些语句的。
于是import的时候使用 __name__ =="__main__" 保护起来就可以了。
'''
if __name__ =="__main__":
print("当前进程ID:",os.getpid())
# 创建进程
p1 = Process(target=func1, args=('p1',))
p2 = Process(target=func1, args=('p2',))
p1.start()
p2.start()
进程的创建方式(继承Process类)
和使用Thread 类创建子线程的方式非常类似,使用 Process 类创建实例化对象,其本质是调用该类的构造方法创建新进程。Process 类的构造方法格式如下:
def __init__(self,group=None,target=None,name=None,args=(),kwargs={})
其中,各个参数的含义为:
group
:该参数未进行实现,不需要传参;
target
:为新建进程指定执行任务,也就是指定一个函数;
name
:为新建进程设置名称;
args
:为 target 参数指定的参数传递非关键字参数;
kwargs
:为 target 参数指定的参数传递关键字参数。
【示例】类的方式创建进程
from multiprocessing import Process
from time import sleep
class MyProcess(Process):
def __init__(self,name):
Process.__init__(self)
self.name = name
def run(self):
print(f"Process:{self.name} start")
sleep(1)
print(f"Process:{self.name} end")
if __name__ == '__main__':
p1 = MyProcess("p1")
p2 = MyProcess("p2")
p1.start()
p2.start()
Queue实现进程间通信
前面讲解了使用 Queue
模块中的 Queue
类实现线程间通信,但要实现进程间通信,需要使用 multiprocessing
模块中的 Queue
类。
简单的理解 Queue
实现进程间通信的方式,就是使用了操作系统给开辟的一个队列空间,各个进程可以把数据放到该队列中,当然也可以从队列中把自己需要的信息取走。
【示例】使用Queue实现进程间通信的经典代码
from multiprocessing import Process, Queue
from time import sleep
class MyProcess(Process):
def __init__(self,name,mq):
Process.__init__(self)
self.name = name
self.mq = mq
def run(self):
print(f"Process:{self.name} start")
print('主进程存放的数据:', self.mq.get()) #打印主进程存放的数据
sleep(2)
# 在子进程里存放数据,可以使用主进程提取
self.mq.put(f'子进程存放的数据:{self.name}')
print(f"Process:{self.name} end")
if __name__ == '__main__': #创建主进程
mq = Queue()
'''mq不可以作为普通的全局变量,
创建子进程时需要作为参数传进子进程,类里面也要写self.mq而不是mq,
因为mq在当前进程为全局变量,但是mq会与创建的新的进程没有关系,从而导致报错'''
#在主进程里存放数据,可以使用子进程提取
mq.put('1') #当字符串'1'被使用后会消失
mq.put('2') #所以下次进程会使用下一条数据字符串'2'
mq.put('3')
#进程列表
p_list = [] #将进程全部存放在列表内
for i in range(3):
p = MyProcess(f'p{i}',mq) #创建子进程
p_list.append(p)
for p in p_list: #启动被存放在列表内的子进程
p.start()
#因为要打印主进程里存放的信息,所以使用join让子进程先结束
for p in p_list:
p.join()
'''使用join之后,可以确保join后面的代码在子进程运行(存放数据)结束后运行
如果不使用join,则join后面的代码可能在子进程运行(存放数据)之前运行,
数据还没有存放,那么后面就无法打印子进程存放的数据'''
print(mq.get()) #打印子进程里存放的数据
print(mq.get())
print(mq.get())
Pipe实现进程间通信
Pipe 直译过来的意思是“管”或“管道”,和实际生活中的管(管道)是非常类似的。
Pipe方法返回(conn1, conn2)代表一个管道的两个端。
Pipe方法有duplex参数,如果duplex参数为True(默认值),那么这个参数是全双工模式,也就是说conn1和conn2均可收发。若duplex为False,conn1只负责接收消息,conn2只负责发送消息。send和recv方法分别是发送和接受消息的方法。例如,在全双工模式下,可以调用conn1.send发送消息,conn1.recv接收消息。如果没有消息可接收,recv方法会一直阻塞。如果管道已经被关闭,那么recv方法会抛出EOFError。
【示例】使用Pipe管道实现进程间通信
#coding=utf-8
import multiprocessing
from time import sleep
def func1(conn1):
sub_info = "Hello!"
print(f"进程1--{multiprocessing.current_process().pid}发送数据:{sub_info}")
sleep(1)
conn1.send(sub_info) #发送进程1的数据
print(f"来自进程2:{conn1.recv()}")
sleep(1)
def func2(conn2):
sub_info = "你好!"
print(f"进程2--{multiprocessing.current_process().pid}发送数据:{sub_info}")
sleep(1)
conn2.send(sub_info) #发送进程2的数据
print(f"来自进程1:{conn2.recv()}")
sleep(1)
if __name__ == '__main__':
#创建管道
conn1,conn2 = multiprocessing.Pipe()
# 创建子进程
process1 = multiprocessing.Process(target=func1,args=(conn1,))
process2 = multiprocessing.Process(target=func2,args=(conn2,))
# 启动子进程
process1.start()
process2.start()
Manager管理器
管理器提供了一种创建共享数据的方法,从而可以在不同进程中共享。
【示例】管理器Manager实现进程通信
#coding=utf-8
from multiprocessing import Process,current_process
from multiprocessing import Manager
def fun1(name,m_list,m_dict):
m_list.append('你好') #子进程往列表里添加数据
m_dict['name'] = '尚学堂' #子进程往字典里添加数据
if __name__ == "__main__":
#使用后需要关闭管理器,所以用with自动关闭
with Manager() as mgr:
m_list = mgr.list()
m_dict = mgr.dict()
m_list.append('Hello!!') #主进程里往列表增加信息
#两个进程不能直接互相使用对象,需要互相传递
p1 = Process(target=fun1,args=('p1',m_list,m_dict))
p1.start()
p1.join() #等p1进程结束,主进程继续执行
print(m_list)
print(m_dict)
进程池(Pool)
Python提供了更好的管理多个进程的方式,就是使用进程池。
进程池可以提供指定数量的进程给用户使用,即当有新的请求提交到进程池中时,如果池未满,则会创建一个新的进程用来执行该请求;反之,如果池中的进程数已经达到规定最大值,那么该请求就会等待,只要池中有进程空闲下来,该请求就能得到执行。
使用进程池的优点:
- 提高效率,节省开辟进程和开辟内存空间的时间及销毁进程的时间
- 节省内存空间
类/方法 | 功能 | 参数 |
---|---|---|
Pool(processes) | 创建进程池对象 | processes表示进程池中有多少进程 |
pool.apply_async(func,args,kwds) | 异步执行 ;将事件放入到进程池队列 | func 事件函数 args 以元组形式给func传参kwds 以字典形式给func传参 返回值:返回一个代表进程池事件的对象,通过返回值的get方法可以得到事件函数的返回值 |
pool.apply(func,args,kwds) | 同步执行;将事件放入到进程池队列 | func 事件函数 args 以元组形式给func传参 kwds 以字典形式给func传参 |
pool.close() | 关闭进程池 | |
pool.join() | 回收进程池 | |
pool.map(func,iter) | 类似于python的map函数,将要做的事件放入进程池 | func 要执行的函数 iter 迭代对象 |
【示例】进程池使用案例
#coding=utf-8
from multiprocessing import Pool
import os
from time import sleep
def func1(name):
print(f"当前进程的ID:{os.getpid()},{name}")
sleep(2)
return name #返回数据,可以传给callback里的func2
def func2(args):
print(args)
if __name__ == "__main__":
pool = Pool(5) #表示进程池中有5个进程
pool.apply_async(func = func1,args=('sxt1',),callback=func2)
#callback表示把func1返回的数据传给func2
pool.apply_async(func = func1,args=('sxt2',),callback=func2)
pool.apply_async(func = func1,args=('sxt3',),callback=func2)
pool.apply_async(func = func1,args=('sxt4',))
pool.apply_async(func = func1,args=('sxt5',))
pool.apply_async(func = func1,args=('sxt6',)) #从第5个进程开始循环使用进程池里的进程
pool.apply_async(func = func1,args=('sxt7',))
pool.apply_async(func = func1,args=('sxt8',))
pool.close() #进程池使用完需要关闭
pool.join() #保证子进程在主进程结束前运行完
【示例】使用with管理进程池
#coding=utf-8
from multiprocessing import Pool
import os
from time import sleep
def func1(name):
print(f"当前进程的ID:{os.getpid()},{name}")
sleep(2)
return name
if __name__ == "__main__":
with Pool(5) as pool:
args = pool.map(func1,('sxt1,','sxt2,','sxt3,','sxt4,','sxt5,','sxt6,','sxt7,','sxt8,'))
for a in args:
print(a)