多进程 multiprocessing 模块进程并发Process;Pool ;Queue队列 、threading模块;
multiprocessing 模块中的 Process类提供了跨平台的多进程功能,在windows和linux系统都可以使用。
1、首先要实例化一个类,传入要执行的函数。
实例名 = Process(target=<要执行的函数名>)
2、调用实例的.start()方法启动进程
实例名.start()
Process语法结构如下:
Process([group [, target [, name [, args [, kwargs]]]]])
-
target:表示这个进程实例所调用对象;
-
args:表示调用对象的位置参数元组;
-
kwargs:表示调用对象的关键字参数字典;
-
name:为当前进程实例的别名;
-
group:大多数情况下用不到;
Process常用方法:
is_alive() :判断进程实例是否还在运行
join([timeout]) :是否等待子进程实例执行结束,或者等待指定秒数再继续执行
start() :启动进程实例(创建子进程)
run() :如果没有给定target参数,对这个对象调用start()方法时,执行该对象的run()方法
terminate() : 立即终止子进程
Process常用属性:
name :当前进程实例别名,默认为Process-N;N为从1开始递增的证书
pid :当前进程实例的pid
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # @Time: 2020/6/29 16:45 # @Author:zhangmingda # @File: multiprocessing_study.py # @Software: PyCharm # Description:multiprocessing 模块学习 from multiprocessing import Process import time def test(): while True: print('----Test-----') time.sleep(1) if __name__ == '__main__': p = Process(target=test) p.start() #启动子进程执行test函数 while True: print('---main主进程----') time.sleep(1)
输出
---main主进程----
----Test-----
---main主进程----
----Test-----
................
与fork的区别,Process方式的多进程:子进程不结束,主进程不会退出
示例代码:主进程代码瞬间执行到最后,子进程未结束,命令提示符不会出现
# cat multiprocessing_study.py
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # @Time: 2020/6/29 16:45 # @Author:zhangmingda # @File: multiprocessing_study.py # @Software: PyCharm # Description:multiprocessing 模块学习 from multiprocessing import Process import time def test(): while True: print('----Test-----') time.sleep(1) if __name__ == '__main__': p = Process(target=test) p.start() #启动子进程执行test函数
# python multiprocessing_study.py
----Test-----
----Test-----
----Test-----
----Test-----
Process.join([timeout]) 阻塞主进程; 等待子进程执行结束,或者阻塞主进程指定秒数
from multiprocessing import Process import time def test(): for i in range(0,5): print('----Test-----%s',i) time.sleep(1) if __name__ == '__main__': p = Process(target=test) p.start() #启动子进程执行test函数 p.join(3) print('___main_______')
执行输出效果
----Test-----%s 0 ----Test-----%s 1 ----Test-----%s 2 ___main_______ ----Test-----%s 3 ----Test-----%s 4
通过类的继承:子类方式的方式创建子进程实例
创建新的进程还能够使用类的方式,可以自定义一个类,继承Process类,每次实例化这个类的时候,就等同于实例化一个进程对象,请看下面的实例:
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # @Time: 2020/6/29 19:03 # @Author:zhangmingda # @File: process_study.py # @Software: PyCharm # Description:学习类的继承方式创建新进程 from multiprocessing import Process import time import os class SubProgress(Process): def __init__(self,interval): # 因为Process类本身也有__init__方法,这个子类相当于重写了这个方法, # 但这样就会带来一个问题,我们并没有完全的初始化一个Process类,所以就不能使用从这个类继承的一些方法和属性, # 最好的方法就是将继承类本身传递给父类Process.__init__方法,完成这些初始化操作, # 有两种方式:1. 直接用父类名字.__init__() 2.用super(子类名,self).__init__() super(SubProgress,self).__init__() # Process.__init__(self) self.interval = interval def run(self): """重写Process类中的run()方法""" print("子进程%s开始执行,父进程为%s" % (os.getpid(),os.getppid())) start_timestamp = time.time() time.sleep(self.interval) stop_timestamp = time.time() print("子进程:%s执行结束,耗时:%s 秒" % (os.getpid(),stop_timestamp - start_timestamp)) if __name__ == '__main__': t_start = time.time() print("当前程序进程%s" % os.getpid()) p1 = SubProgress(1) #对一个不包含target属性的Process类实例化对象执行start()方法,会运行类中的run()方法 p1.start() p1.join() t_stop = time.time() print('主进程%s执行结束,耗时:%s秒' % (os.getpid(),t_stop - t_start))
进程池Pool
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行,请看下面的实例:
Pool().apply_async(worker任务函数,(参数,...))
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # @Time: 2020/6/29 19:50 # @Author:zhangmingda # @File: Pool_study.py # @Software: PyCharm # Description:进程池使用 from multiprocessing import Pool import os import time import random def worker(task_num): for i in range(3): print("子进程:%s 任务%s: 步骤:%s"% (os.getpid(),task_num,i)) time.sleep(1) if __name__ == '__main__': # Pool(3)里面的3表示该进程池里面可以同时跑几个进程 pool = Pool(3) for j in range(10): print('向进程池中添加第%s 个任务' % j) #如果数量超过了进程池,会暂存在进程池中,for循环不会阻塞 pool.apply_async(worker,(j,)) #向进程池中添加任务的时候同时给任务执行的函数传递参数 pool.close() #禁止再向进程池中添加任务 pool.join() #等待进程池所有任务结束再结束主进程。否则主进程结束子进程随之死亡不会执行
进程池输出效果如下
D:\Python3_study\tornado1\Scripts\python.exe D:/Python3_study/tornado1/test/Pool_study.py 向进程池中添加第0 个任务 向进程池中添加第1 个任务 向进程池中添加第2 个任务 向进程池中添加第3 个任务 向进程池中添加第4 个任务 向进程池中添加第5 个任务 向进程池中添加第6 个任务 向进程池中添加第7 个任务 向进程池中添加第8 个任务 向进程池中添加第9 个任务 子进程:35644 任务0: 步骤:0 子进程:35888 任务1: 步骤:0 子进程:2260 任务2: 步骤:0 子进程:35644 任务0: 步骤:1 子进程:35888 任务1: 步骤:1 子进程:2260 任务2: 步骤:1 子进程:35644 任务0: 步骤:2 子进程:35888 任务1: 步骤:2 子进程:2260 任务2: 步骤:2 子进程:35644 任务3: 步骤:0 子进程:35888 任务4: 步骤:0 子进程:2260 任务5: 步骤:0 子进程:35644 任务3: 步骤:1 子进程:35888 任务4: 步骤:1 子进程:2260 任务5: 步骤:1 子进程:35644 任务3: 步骤:2 子进程:35888 任务4: 步骤:2 子进程:2260 任务5: 步骤:2 子进程:35644 任务6: 步骤:0 子进程:35888 任务7: 步骤:0 子进程:2260 任务8: 步骤:0 子进程:35644 任务6: 步骤:1 子进程:35888 任务7: 步骤:1 子进程:2260 任务8: 步骤:1 子进程:35644 任务6: 步骤:2 子进程:35888 任务7: 步骤:2 子进程:2260 任务8: 步骤:2 子进程:35644 任务9: 步骤:0 子进程:35644 任务9: 步骤:1 子进程:35644 任务9: 步骤:2 Process finished with exit code 0
多进程fork;multiprocessing的Process、Pool的区别
一般fork不用,知道即可。无法跨平台。相对比较底层
Process :一般用在主进程和子进程同时执行任务时使用
Pool进程池: 一般主进程用来等待,每个子进程用来执行具体的任务
进程间通讯Queue队列(from multiprocessing import Queue)
队列的特点:先进先出
q = Queue(3) #表示初始化一个Queue对象,最多可接收三条put消息;当队列满了的时候,再推送会阻塞等待队列被读取有空闲空间后才能继续向队列推送内容。
说明
初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);
-
Queue.qsize():返回当前队列包含的消息数量;
-
Queue.empty():判断队列是否为空返回True/False
-
Queue.full():判断队列是否已满返回True/False
-
Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除。
block默认值为True,表示无消息可取时,是否阻塞,一直等着新消息来。1. 如果block使用默认值True,消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止;
2. 如果block值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;
3. 如果设置了timeout (block使用默认值True),则会等待timeout秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;
-
Queue.get_nowait():相当Queue.get(False);
-
Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True (队列满了是否阻塞);
1. 如果block使用默认值True,消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,
2. 果block值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;
3. 如果置了timeout (block=True默认值时),则会等待timeout秒,若还没空间,则抛出"Queue.Full"异常;
- Queue.put_nowait(item):相当Queue.put(item, False);
示例:
如下示例等于将父进程中的q克隆了一份给子进程,子进程进行了序列化,在子进程中执行后又反序列化给父进程。所以在父进程中能够得到子进程对父进程中数据的修改结果。
进程间通信示例代码 (注意:from multiprocessing import的 Queue队列,只能在from multiprocessing import的Process创建的进程之间共享数据 )
#!/usr/bin/env python # Author:Zhangmingda import queue,threading from multiprocessing import Process,Queue def f(q): q.put([234,None,'Hello']) if __name__ == '__main__': # q = queue.Queue() #使用线程队列运行会报错TypeError: can't pickle _thread.lock objects q = Queue() #进程队列 p = Process(target=f,args=(q,)) #启动一个子进程,传递一个队列给子进程,在子进程中向队列内put一个列表[234,None,'Hello'] # p = threading.Thread(target=f,) #启动线程直接共享主进程内存 p.start() #子进程运行 print(q.get()) #主进程中从队列中获取到了子进程推送的数据 p.join() #等待子进程运行结束,主进程再退出
运行效果
C:\Users\Administrator\Desktop\Python3_study\venv\Scripts\python.exe C:/Users/Administrator/Desktop/Python3_study/day10/进程间通信?.py
[234, None, 'Hello']
Process finished with exit code 0
多进程之间共享数据使用from multiprocessing import Manager 类
案例一:使用for循环 利用Process 创建多个子进程(效果类似一个进程之间多线程)
#!/usr/bin/env python # Author:Zhangmingda from multiprocessing import Process,Manager import os def f(d,l): d[os.getpid()] = os.getpid() #对传入的字典进行赋值 l.append(os.getpid()) #向列表中增加数据 if __name__ == '__main__': with Manager() as manager: d = manager.dict() l = manager.list(range(5)) print('初始化一个进程间可以共享的字典和列表:',d,l) p_list = [] #准备存储进程实例 for i in range(10): #准备启动10个进程 p = Process(target=f,args=(d,l)) p.start() print('启动第%d个进程'%(i+1)) p_list.append(p) for res in p_list: res.join() #等待所有进程运行结束 print(d,l) #主进程获得了子进程修改后的数据
运行效果
C:\Users\Administrator\Desktop\Python3_study\venv\Scripts\python.exe C:/Users/Administrator/Desktop/Python3_study/day10/进程间数据共享manager.py 初始化一个进程间可以共享的字典和列表: {} [0, 1, 2, 3, 4] 启动第1个进程 启动第2个进程 启动第3个进程 启动第4个进程 启动第5个进程 启动第6个进程 启动第7个进程 启动第8个进程 启动第9个进程 启动第10个进程 {6632: 6632, 5300: 5300, 6044: 6044, 2212: 2212, 3828: 3828, 5184: 5184, 5412: 5412, 7000: 7000, 5768: 5768, 5784: 5784} [0, 1, 2, 3, 4, 6632, 5300, 6044, 2212, 3828, 5184, 5412, 7000, 5768, 5784] Process finished with exit code 0
案例二:Pool进程池处理的多个进程之间共享数据
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # @Time: 2020/6/30 13:46 # @Author:zhangmingda # @File: multiprocessing_Manager_study.py # @Software: PyCharm # Description:多进程之间共享数据(通信)示例代码,多进程使用进程池Pool的方式 from multiprocessing import Manager,Pool import os def reader(q): """读取进程池中的数据""" print("reader()启动,当前进程:%s, 父进程:%s" % (os.getpid(),os.getppid())) for i in range(q.qsize()): print("reader()读取到队列中的消息:%s" % q.get()) def writer(q): print("writer()启动,当前进程:%s, 父进程:%s" % (os.getpid(),os.getppid())) for i in "ZhangMingDa": q.put(i) if __name__ == '__main__': print('主进程启动,PID:%s' % os.getpid()) #初始化共享消息的队列 q = Manager().Queue() # 准备进程池实例 pool = Pool() # 使用阻塞模式创建进程,这样就无需在reader中写死循环了,可以writer()后再reader() pool.apply(writer,(q,)) pool.apply(reader,(q,)) pool.close() # 封闭进程池,禁止再添加任务 pool.join() # 等待子进程执行结束再退出主进程 print("主进程结束:PID:%s" % os.getpid())
执行效果输出
D:\Python3_study\tornado1\Scripts\python.exe D:/Python3_study/tornado1/test/multiprocessing_Manager_study.py
主进程启动,PID:13888
writer()启动,当前进程:16536, 父进程:13888
reader()启动,当前进程:14028, 父进程:13888
reader()读取到队列中的消息:Z
reader()读取到队列中的消息:h
reader()读取到队列中的消息:a
reader()读取到队列中的消息:n
reader()读取到队列中的消息:g
reader()读取到队列中的消息:M
reader()读取到队列中的消息:i
reader()读取到队列中的消息:n
reader()读取到队列中的消息:g
reader()读取到队列中的消息:D
reader()读取到队列中的消息:a
主进程结束:PID:13888
Process finished with exit code 0
案例三:多进程复制文件
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # @Time: 2020/6/30 22:28 # @Author:zhangmingda # @File: Pool_Queue_copy_file.py # @Software: PyCharm # Description:多进程拷贝文件代码示例 from multiprocessing import Pool, Manager #使用Pool进程池执行多个进程,Manager().Queue() 进程间通信 import os import sys def copy_file(old_dir_name,new_dir_name,filename,queue): """ :param old_dir_name: :param new_dir_name: :param filename: :param queue: :return: """ queue.put(filename) #这里有个待解的疑问:放在函数第一行,所有文件名都能添加到队列里面 rf = open(os.path.join(old_dir_name, filename), 'r') wf = open(os.path.join(new_dir_name,filename), 'w') content = rf.read() # 读取所有数据 wf.write(content) rf.close() wf.close() #queue.put(filename) 这句话如果放到这里跑,队列里面就只能put进去2个文件名 ,然后就没有然后了,导致整个程序卡在main()主进程的while循环里面 def main(): old_dir_name = sys.argv[1] new_dir_name = old_dir_name + "副本" #创建新文件夹 os.mkdir(new_dir_name) filenames = os.listdir(old_dir_name) pool = Pool(4) queue = Manager().Queue() #存储已完成的文件的文件名 for filename in filenames: pool.apply_async(copy_file,(old_dir_name,new_dir_name,filename,queue)) old_file_num = len(filenames) while queue.qsize() <= old_file_num: print("\r 已拷贝文件%s/%s" % (queue.qsize(),old_file_num),end='') if queue.qsize() == old_file_num: print("完成") break if __name__ == '__main__': main()
(实际进程间还是copy了同样的数据最后汇总,Manager()自己内部有锁)
Pool进程池任务返回值获取和使用
进程池 Pool+回调函数示例代码
#!/usr/bin/env python # Author:Zhangmingda from multiprocessing import Pool import time,os def Foo(i): time.sleep(1) print('in process',os.getpid()) return i + 100 #return 的值可以被当做参数传给回调函数获取 def Bar(arg): # 函数作为回调函数使用时;形参自动被赋值为进程池执行任务的返回值 print('-->exec done:',arg,os.getpid()) if __name__ == '__main__': pool = Pool(processes=3) #限制可以同时运行的进程数量 print('主进程ID:',os.getpid()) for i in range(10): # pool.apply(func=Foo,args=(i,)) #串行 # pool.apply_async(func=Foo,args=(i,)) #并行 pool.apply_async(func=Foo,args=(i,),callback=Bar) #并行+回调主进程运行 print('for 循环 END。。。。。') pool.close() pool.join() #等待所有进程运行结束,否则会直接结束运行。
运行效果
C:\Users\Administrator\Desktop\Python3_study\venv\Scripts\python.exe C:/Users/Administrator/Desktop/Python3_study/day10/进程池(限制同时运行的进程数量).py
主进程ID: 5508
for 循环 END。。。。。
in process 6856
-->exec done: 100 5508
in process 6620
-->exec done: 101 5508
in process 7988
-->exec done: 102 5508
in process 6856
-->exec done: 103 5508
in process 6620
-->exec done: 104 5508
in process 7988
-->exec done: 105 5508
in process 6856
-->exec done: 106 5508
in process 6620
-->exec done: 107 5508
in process 7988
-->exec done: 108 5508
in process 6856
-->exec done: 109 5508
Process finished with exit code 0
进程无法使用queue线程队列,threading线程才可以使用
示例代码:
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # @Time: 2020/7/1 21:54 # @Author:zhangmingda # @File: threading_Thread_Queue.py # @Software: PyCharm # Description: #!/usr/bin/env python # Author:Zhangmingda import queue #线程队列 import threading #多线程模块 from multiprocessing import Process,Queue #多进程 进程队列 def f(q): q.put([234,None,'Hello']) if __name__ == '__main__': q = queue.Queue() #使用线程队列 # p = Process(target=f,args=(q,)) #启动一个子进程 运行报错TypeError: can't pickle _thread.lock objects p = threading.Thread(target=f,args=(q,)) #启动线程直接共享主进程内存 p.start() print(q.get()) p.join()
C:\Users\Administrator\Desktop\Python3_study\venv\Scripts\python.exe C:/Users/Administrator/Desktop/Python3_study/day10/进程间通信?.py Traceback (most recent call last): File "C:/Users/Administrator/Desktop/Python3_study/day10/进程间通信?.py", line 13, in <module> p.start() File "C:\Program Files\Python36\lib\multiprocessing\process.py", line 105, in start self._popen = self._Popen(self) File "C:\Program Files\Python36\lib\multiprocessing\context.py", line 223, in _Popen return _default_context.get_context().Process._Popen(process_obj) File "C:\Program Files\Python36\lib\multiprocessing\context.py", line 322, in _Popen return Popen(process_obj) File "C:\Program Files\Python36\lib\multiprocessing\popen_spawn_win32.py", line 65, in __init__ reduction.dump(process_obj, to_child) File "C:\Program Files\Python36\lib\multiprocessing\reduction.py", line 60, in dump ForkingPickler(file, protocol).dump(obj) TypeError: can't pickle _thread.lock objects
posted on 2018-07-26 23:03 zhangmingda 阅读(443) 评论(0) 编辑 收藏 举报