multiprocessing
进程概述:一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。
进程创建类似线程,通过multiprocessing模块的Process类创建进程。类的括号内部也可以传入target=方法名和用args=()传入实参到要执行的方法内部,args也必须为元组。
一个简单的多进程实例:
1 import multiprocessing 2 import time 3 4 5 def test(): 6 for i in range(5): 7 print('测试代码1', end='\n') 8 time.sleep(1) 9 10 11 def dance(): 12 for i in range(5): 13 print("测试代码2", end='\n') 14 time.sleep(1) 15 16 17 if __name__ == '__main__': 18 p1 = multiprocessing.Process(target=test) 19 p2 = multiprocessing.Process(target=dance) 20 p1.start() # 启动线程,即让线程开始执行 21 p2.start()
运行结果:
1 测试代码1 2 测试代码2 3 测试代码1 4 测试代码2 5 测试代码1 6 测试代码2 7 测试代码1 8 测试代码2 9 测试代码1 10 测试代码2
进程和线程的对比:
1 import multiprocessing 2 3 4 def down_from_web(q): 5 """从网站下载数据""" 6 # 模拟从网上下载数据 7 data = [11, 22, 33, 44] 8 # 将数据写入队列 9 for temp in data: 10 q.put(temp) 11 12 print("___下载器下载完成___") 13 14 15 def analysis_data(q): 16 """数据处理""" 17 # 从队列获取数据 18 waitting_analysis_data = list() 19 while True: 20 data = q.get() 21 waitting_analysis_data.append(data) 22 if q.empty(): 23 break 24 # 模拟数量数据 25 print(waitting_analysis_data) 26 27 28 def main(): 29 # 1.创建一个Queue 30 q = multiprocessing.Queue() 31 32 # 2.创建多进程 33 p1 = multiprocessing.Process(target=down_from_web, args=(q, )) 34 p2 = multiprocessing.Process(target=analysis_data, args=(q, )) 35 p1.start() 36 p2.start() 37 38 39 if __name__ == '__main__': 40 main()
运行结果:
1 ___下载器下载完成___ 2 [11, 22, 33, 44]
进程池Pool:
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Pocess动态生成多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。
进程池创建: 变量名 = multiprocessing.Pool(int)
进程池案例:
1 from multiprocessing import Pool 2 import os, time, random 3 4 5 def worker(msg): 6 t_start = time.time() # 获取开始时间 7 print("%s开始执行,进程号为%d" % (msg, os.getpid())) 8 # random.random()随机生成0~1之间的浮点数 9 time.sleep(random.random()*2) 10 t_stop = time.time() # 获取执行结束时间 11 print(msg, "执行完毕,耗时%.2f" % (t_stop-t_start)) 12 13 14 def main(): 15 # 定义一个线程池,最大线程数是3 16 po = Pool(3) 17 for i in range(0, 10): 18 po.apply_async(worker, (i,)) 19 20 print("___start___") 21 po.close() 22 po.join() 23 print("___end___") 24 25 26 if __name__ == '__main__': 27 main()
运行效果:
1 ___start___ 2 0开始执行,进程号为9744 3 1开始执行,进程号为9172 4 2开始执行,进程号为9836 5 2 执行完毕,耗时0.10 6 3开始执行,进程号为9836 7 0 执行完毕,耗时0.59 8 4开始执行,进程号为9744 9 3 执行完毕,耗时0.46 10 5开始执行,进程号为9836 11 5 执行完毕,耗时0.22 12 6开始执行,进程号为9836 13 4 执行完毕,耗时0.36 14 7开始执行,进程号为9744 15 1 执行完毕,耗时1.89 16 8开始执行,进程号为9172 17 8 执行完毕,耗时0.06 18 9开始执行,进程号为9172 19 7 执行完毕,耗时1.03 20 6 执行完毕,耗时1.36 21 9 执行完毕,耗时1.77 22 ___end___ 23 24 Process finished with exit code 0
注意:如果是windows系统下创建进程池运行程序时,进程池相关代码应该放在if __name__ == '__main__'下面,否则会出现程序报错。详见:https://www.cnblogs.com/zuzhuangmengxiang/p/12663478.html
综合案例(需要主进程和子进程间通信):
简介:通过多进程拷贝一个文件夹下的所有文件到一个新的文件夹内,进程数做多为3个,程序开始后打印拷贝进度条。
代码如下:
1 import multiprocessing 2 import os 3 4 5 def copy_file(q, file_name, old_folder_name, new_folder_name): 6 """读取并拷贝文件""" 7 # print("模拟拷贝文件。。。%s" % file_name) 8 old_f = open(old_folder_name+'/'+file_name, 'rb') 9 content = old_f.read() 10 11 new_f = open(new_folder_name+'/'+file_name, 'wb') 12 new_f.write(content) 13 # print("已拷贝文件 %s" % file_name) 14 q.put(file_name) 15 16 17 def main(): 18 # 1.获取用户要copy的文件夹的名字 19 old_folder_name = input("请输入您要拷贝的文件夹名:") 20 21 # 2.创建一个新的文件夹 22 new_folder_name = old_folder_name+'[复件]' 23 if not os.path.exists(new_folder_name): 24 os.mkdir(new_folder_name) 25 26 # 3.获取要copy的文件夹下所有的文件名称 listdir() 27 file_names = os.listdir(old_folder_name) 28 29 # 4.创建进程池 30 po = multiprocessing.Pool(5) 31 32 # 5.创建队列 —— multiprocessing.Manager().Queue()能够让主进程和子进程间进行通信 33 q = multiprocessing.Manager().Queue() 34 35 # 6.拷贝文件到新的文件夹内 36 for file_name in file_names: 37 po.apply_async(copy_file, (q, file_name, old_folder_name, new_folder_name)) 38 po.close() 39 # po.join() 40 41 # 用已下载个数/总个数显示进度条 42 len_file = len(file_names) 43 down_ok_num = 0 44 while True: 45 q.get() 46 down_ok_num += 1 47 print("已下载进度: %.02f %%" % (down_ok_num*100/len_file)) 48 if down_ok_num >= len_file: 49 break 50 51 52 if __name__ == '__main__': 53 main()
运行结果:
1 请输入您要拷贝的文件夹名:test 2 已下载进度: 0.58 % 3 已下载进度: 1.17 % 4 已下载进度: 1.75 % 5 已下载进度: 2.34 % 6 ...... 7 已下载进度: 100.00 %
小改进:进度条要放在一行显示,只需百分比变动。print()内最左边加\r表示行首显示,尾部加end=''表示每次循环打印的内容紧挨下次打印内容间。改进如下:
1 print("\r已下载进度: %.02f %%" % (down_ok_num * 100 / len_file), end='')
运行结果:
1 请输入您要拷贝的文件夹名:test 2 已下载进度: 100.00 %