上期讲解了:python多线程
那么这一节说说python中的多进程,那么它与线程有什么联系,在python中应该怎么实现
进程的概念
简单来说进程就是正在进行的程序,在windows中的任务管理器可以看到当前正在运行的程序,
也就是进程。
进程与程序的区别: 简单来说程序是一个没有运行的软件,而进程是已经运行起来的程序,
进程可以对操作系统执行一些特殊命令,比如打开摄像头,使用键盘输入法等等
在任务管理器中点击结束运行时,进程就结束了。
程序:例如xxx.py这是程序,它是静态的
进程:一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。不仅可以通过线程完成多任务,进程也是可以的
用python实现多进程
在python中可以使用multiprocessing模块完成多进程,使用multiprocessing下面的Process模块即可创建进程 下面举一个简单的例子
import multiprocessing
import time
def test1():
while True:
print("-----1------")
time.sleep(1)
def test2():
while True:
print("-----2------")
time.sleep(1)
def main():
# 构造进程列表
process_list = []
# 创建进程: 使用multiprocessing.Process可以创建进程,里面传入的参数与线程一致
p1 = multiprocessing.Process(target=test1)
process_list.append(p1)
p2 = multiprocessing.Process(target=test2)
process_list.append(p2)
print(process_list)
for p in process_list:
p.start() # 执行进程
if __name__ == '__main__':
main()
进程和线程的对比
线程和进程都能实现多任务,但是进程所占的资源比线程要多,这是因为在程序运行时每个子线程会复制一份代码在内存中,导致资源占用率大
进程、线程对比:
- 进程,能够完成多任务,比如在一台电脑上能够运行多个QQ
- 线程:能够完成多任务,比如在QQ上的多个窗口
小结 - 一个程序至少有一个进程,一个进程至少有一个线程。
- 线程的划分尺度小于进程(资源占用率比进程少),使得多线程程序的并发性高
- 进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大的提高了程序的运行效率
进程与线程的关系:
- 线程不能够独立运行,必须存在于进程中
- 可以将进程理解为工厂中的一条流水线,而其中的线程就是流水线上的工人
进程和线程的优缺点:
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。
通过队列完成进程间通信共享的问题
python中队列的概念:
Python的 multiprocessing.Queue()模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,这些队列都实现了锁原语,能够在多任务中直接使用。可以使用队列来实现线程间的同步。
这里我也举了一个例子,代码中相应的功能我都写了注释,所以在这就不多赘述了。
import multiprocessing
def download_from_web(q):
"""从网上获取的数据"""
datas = [2, 3, 4, 5, 12, 23, 12] # 模拟从网上下载下来的数据
for data in datas: # 循环打印数据,将遍历的内容放到队列中去
q.put(data) # 放入数据
print("----数据已放到队列中----")
def parse_data(q):
"""数据处理"""
q_values = list() # 创建一个空列表,这样可读性更强
while True: # 一直取出队列中的值,当队列为空时,结束循环
data = q.get() # 取出数据,一次只能取一个
q_values.append(data)
if q.empty(): # 如果队列中已经没有元素了, 则退出循环
break
print("q_values中的值是:", q_values) # 打印出列表中的值
def main():
# 创建队列
q = multiprocessing.Queue() # 括号中不指定参数时,默认可以方最多的数量
# 创建进程
t1 = multiprocessing.Process(target=download_from_web, args=(q,))
t2 = multiprocessing.Process(target=parse_data, args=(q,))
t1.start() # 启动进程
t2.start()
if __name__ == '__main__':
main()
运行程序,返回结果:
进程池的概念
进程池:
当需要创建子进程数量不多时,可以直接利用multiprocessing中的Process动态生成多个进程,
但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块
提供的Pool方法。
初始化Pool池,可以指定一个最大进程数,当有新的请求提交到pool中是,如果池还没有满,那么就会创建
一个新的进程来执行该请求,但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,知道
池中有进程结束,才会用之前的进程执行新的任务。
进程池: 通俗的可以理解为,当有100个人要坐船时, 但只有10艘船,那么就可以利用进程池的特点
先让10个人上船,当这10个人游玩完了之后,再安排下一组10个人上船。循环利用,这就是进程池的特点。
进程池的创建
import multiprocessing
import os
import random
import time
def worker(msg):
t_start = time.time()
print("%s开始执行, 进程号为%d" % (msg, os.getpid())) # 返回进程号
# random.random()随机生成0-1之间的浮点数
time.sleep(random.random() * 2)
t_stop = time.time() # 程序结束时间
print(msg, "执行完毕, 耗时%.2f" % (t_stop - t_start))
def main():
# 创建线程池
po = multiprocessing.Pool(3) # 定义一个进程池,最大进程数为3
for i in range(0, 10): # 假装定义有10个任务
# Pool().apply_async(要调用的目标, (传递给目标的参数元组,))
# 每次循环将会用空闲出来的子进程取调用目标
po.apply_async(worker, (i,))
# 开始执行
print("----------start--------")
po.close() # 关闭进程池, 关闭后pool不在接收新的请求
po.join() # 等待子线程先执行完毕后,主线程开始执行,注意:join()方法必须放在close()方法后
print("----------end--------")
if __name__ == '__main__':
main()
运行程序,结果是
可以看到,程序中一给你个有10个任务,通过进程编号可以得知当前一共有3个进程,这3个进程同时开始执行任务,当有一个任务完成时,这个进程回去执行下一个任务,知道任务执行完毕为止。
总结: 显示进度条文件提取器
在最后根据以上的知识写了一个可以显示进度条的多进程办的文件提取器
下面直接上代码,复制粘贴即可使用
import os
import multiprocessing
from multiprocessing import Manager # 使用Manager来创建队列
def copy_file(q, file_name, old_file_name, new_file_name):
# 完成目标抓取
# print("正在模拟copy文件传递:从%s--->到%s, 文件: %s" % (old_file_name, new_file_name, file_name))
old_f = open(old_file_name + "/" + file_name, "rb")
content = old_f.read()
old_f.close()
new_f = open(new_file_name + "/" + file_name, "wb")
new_f.write(content)
new_f.close()
# 如果拷贝完文件,就向队列中写入一个消息,表示已完成
q.put(file_name)
def main():
old_file_name = input("请输入要抓取的文件的绝对路径:")
new_file_name = old_file_name + "附件"
# 创建文件夹
# os.mkdir(r"C:/Users/Administrator/PycharmProjects/06_多进程" + new_file_name)
os.mkdir(new_file_name)
# 获取原文件夹下的所有要抓取的文件名称
file_names = os.listdir(old_file_name)
# 创建队列
q = Manager().Queue()
# 创建进程池
po = multiprocessing.Pool(5)
for file_name in file_names:
po.apply_async(copy_file, args=(q, file_name, old_file_name, new_file_name))
po.close()
po.join() # 将po.join()注释是因为它的下面还有代码,所以即使是主线程先进行完之后,依然会执行后续的代码
all_file_num = len(file_names) # 查看一下所有的文件数量
copy_ok_num = 0
while True:
file_name = q.get()
print("已经完成copy: %s" % file_name)
copy_ok_num += 1
# \r表示直接跳到最后的结果,不打印以前的信息
print("\r拷贝的进度为 %.2f%%" % (copy_ok_num*100/all_file_num), end='')
if copy_ok_num >= all_file_num:
break
print()
if __name__ == '__main__':
main()
注意,在提取文件时,要写入文件在系统的绝对路径,否则程序会失败
下一期会更新python多任务中的多协程。