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.进程,能够完成多任务,比如 在一台电脑上能够同时运行多个QQ
        2.线程,能够完成多任务,比如 一个QQ中的多个聊天窗口
    定义的不同:
        1.进程是系统进行资源分配和调度的一个独立单位
        2.线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计时器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源
    区别:
        1.一个程序至少有一个进程,一个进程至少有一个线程
        2.线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高
        3.进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
        4.线程不能独立进行,必须依存在进程中
        5.可以将进程理解为工厂中的一条流水线,而其中的线程就是这个流水线上的工人
    优缺点:
        线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而线程正相反
Queue队列:
  线程和线程之间如果需要共享资源、进行通信等,需要使用Queue类创建出队列变量,通过队列变量.put()方法向队列写入数据,通过队列变量.get()方法从队列拿取数据。队列变量.full()可以判断队列是够为满,队列变量.empty()可以判断队列是否为空,两个都返回布尔变量。
  当队列中数据存满(或为空)后继续向队列存数据(或取数据)会让程序处于等待状态不再向下执行。为了避免等待,可以使用队列变量.put_nowait()(或队列变量.get_nowait())让程序不用等待,执行结果会以异常的表达形式告知队列已满(或为空)。
  子进程间通信通过 multiprocessing.Queue() 创建,主进程和子进程间通信通过  multiprocessing.Manager().Queue() 创建(后面讲)
  子进程间通过队列通信实例:
 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 %
posted @ 2020-04-09 17:20  组装梦想  阅读(611)  评论(0编辑  收藏  举报