文章目录
- 多进程
- 1. multiprocessing创建子进程
- 2. 自定义类创建子进程
- 3. 进程池
- 4. 多进程复习(以外补充的视频)
- 5. 使用multiprocess.Queue实现进程间通信
- 多进程练习:
多进程
- 简介
任务可以理解为程序,多个程序同时执行 比如:边听歌,边看小说 边写代码,边听相声 单核电脑实现多任务: 调度算法: 时间片轮转(翻牌) 调用优先级(等级) 并发: 3个任务,2个cpu,轮番调度 并行: 4个cpu,3个任务 - 就好比QQ就是主进程,QQ开的对话窗口就是子进程,如果开了三个对话窗口,就好比开了三个子进程。
1. multiprocessing创建子进程
Process(target, name, args)
参数介绍:
- target:标识调用对象,即子进程要执行的任务
- args:标识调用对象的位置参数元组,args=(1,)
- name:为子进程的名称
- Process 类常用方法:
-
process.start()
:启动进程,并调用该子进程中的p.run() -
process.run()
:进程启动时运行的方法,正式它去调用target指定的函数,我们定义类的类中一定要实现的该方法 -
process.terminate()
:(了解)前置终止进程p,不会进行任何清理操作 -
process.is_alive()
:如果p任然运行,返回True,用来判断进程是否还在运行 -
process.join([timeout])
:主进程等待子线程process终止 ,timeout是可选的超时时间
- 全局变量在多个进程中不共享:进程之间的数据是独立的,默认情况下互不影响。
if ____ name ____ == "____ main ____"
:一个python的文件有两种使用方法,第一种是直接作为程序执行;第二种是import到其他的python程序中呗调用(模块重用)执行。
因此if ____ name ____ =="____ main ____" 的作用就是控制这两种情况的执行代码的过程,____name____是内置变量,用于表示当前模块的名字。
在Windows上,子进程会自动运行import 进来的文件。 而在import的时候是会执行这些语句的。如果不加if ____ name ____ == "____ main ____"的话就会无限递归创建子进程。
from multiprocessing import Process
def run(name):
print("子进程运行中,name=%s"%(name))
if __name__ == "__main__":
# 相当于递归的出口,每当子进程创建后,都会自动调用父进程
# 子进程重新加载时候,不再等于"__main__"
print("父进程启动")
p = Process(target=run,args=("test",))
# 创建现成对象
print("子进程将要执行")
p.start()
# 子进程启动
print(p.name)
p.join()
print("子进程结束")
2. 自定义类创建子进程
创建新的进程还能够使用类的方式,可以自定义一个类,继承Process类,每次实例化这个类的时候,就等于实例化一个进城对象。
import multiprocessing
import time
class ClockProcess(multiprocessing.Process):
def run(self): # 要重写父类Process的run()方法
n = 5
while n > 0:
print(n)
time.sleep(1)
n -= 1
if __name__ == "__main__":
p = ClockProcess()
p.start()
p.join()
3. 进程池
- 进程池:创建多个进程。
- 如果是上百个甚至上千个目标,手动的去创建进程工作量巨大,此时就可以用到multiprocess模块中的 Pool。初始化Pool时,可以指定一个 最大进程数。
- multiprocess.Pool常用函数解析:
-
apply_async(func[, args[, kwds]])
:使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func参数列表,kwds(一般不用)为传递给func的关键字参数列表。 -
apply(func[, args[, kwds]])
(了解几乎不用):使用阻塞方式调用func。 -
close()
:关闭进程池,使其不再接受新的任务。 -
terminate()
:无论任务是否完成,立即终止。 -
join()
:主进程阻塞,等待子进程退出,必须在close或terminate之后使用。
from multiprocessing import Pool
import random, time
def work(num):
print(random.random()*num)
time.sleep(3)
if __name__ == "__main__":
po = Pool(3)
# 定义一个进程池对象,最大进程数3,默认为CPU核数
for i in range(10):
po.apply_async(work,(i,))
# apply_async选择要调用的目标,每次循环会用空出来的子进程调用目标
po.close()
po.join()
4. 多进程复习(以外补充的视频)
- 并行:两个任务同时进行。
- 并发:2个CPU同时处理3个任务,看上去是并行的,但其实这两个CPU轮番处理这3个任务。
- python处理多任务的方式:
- 多进程(计算密集型的)
- 多线程(IO密集型的)
- 守护进程的问题:
- daemon:设置为守护进程后,主进程结束后,所有的子线程跟着结束(否则子进程会自动执行==默认)。
- join:阻塞队列中的进程,直到当前进程完成,队列中的进程才会继续
- 常用方法:
- 创建进程池:
Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])
apply(func[, args[, kwds]])
- 同步操作,它会阻塞,直到结果就绪
apply_async(func[, args[, kwds[, callback[, error_callback]]]])
- 异步操作,不会阻塞,可以指定回调函数
close()
join()
- 对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了
terminate()
- 在没有完成未完成的工作的情况下,立即停止工作进程。当池对象被垃圾收集时,terminate()将立即被调用
import random,time,os
from multiprocessing import Pool
#模拟耗时任务
def task(task_id):
start = time.time()
time.sleep(random.random()*5)
end = time.time()
cost = end-start
print('task{0},子进程id为:{1}执行完毕,耗时{2}秒'.format(task_id,os.getpid(),cost))
#创建进程池
if __name__ == '__main__':
pool = Pool()
print(os.cpu_count())
#循环创建5个进程加入到进程池中
for i in range(5):
# 异步
pool.apply_async(task,args=(i,))
# 同步
# pool.apply(task,args=(i,))
#关闭进程池
pool.close()
#阻塞进程池
pool.join()
print("所有子进程执行完毕")
print("主进程id:{0}结束".format(os.getpid()))
5. 使用multiprocess.Queue实现进程间通信
- Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据,我们以Queue做示例。
- Queue就相当于容器,遵循“先进先出”,
- 常用方法:
-
Queue.qsize()
返回当前队列中的消息数量 -
Queue.empty()
返回当前队列是否为空 -
Queue.full()
返回当前队列是否满了 -
Queue.get([block,[timeout]])
获取队列中的一条消息,然后将其从队列中移除block默认为True,block就代表阻塞不阻塞的意思。 -
Queue.get_nowait()
相当于Queue.get(False) -
Queue.put(item,[block,[timeout]])
将item消息写入队列,block默认这为True
- 例子:模拟读写数据
"""
进程间的通信:Queue
注意:进程间通信,最好不要写全局变量
"""
from multiprocessing import Queue,Process
import time,random
#爬取数据
def getData(q):
#模拟爬取到的数据
for i in ['a','b','c']:
#每遍历一次,取一条数据放入共享队列中
q.put(i)
print("存储数据为:%s"%i)
#模拟爬取数据耗时操作
time.sleep(random.random())
#使用数据
def useData(q):
while True:
content = q.get()
print('获取数据为:%s'%content)
if __name__ =='__main__':
#创建一个共享队列对象
queue = Queue()
p1 = Process(target=getData,args=(queue,))
p2 = Process(target=useData,args=(queue,))
p1.start()
p2.start()
p1.join()
if queue.empty():
if p2.is_alive():
#手动终止进程运行
p2.terminate()
if p1.is_alive():
#手动终止进程运行
p1.terminate()
print('主进程结束')
多进程练习:
"""
使用进程池方式实现多任务
复制一个文件夹下的所有 文件(一个文件的复制由一个进程来完成)
文件夹目录:D:/file
注意:需要使用管道队列Manager().Queue()
"""
from multiprocessing import Manager,Pool
import time,random,os
#复制文件
def copyFile(srcFile,toFile,queue): # srcFile:被拷贝的文件夹;toFile:目标文件夹
try:
fr =open(srcFile,'r',encoding='utf-8')
fw =open(toFile,'w',encoding='utf-8')
content = fr.read()
fw.write(content)
#将已经复制完毕的文件 的信息存入队列中
queue.put(toFile)
time.sleep(random.random()*3)
finally:
fr.close()
fw.close()
if __name__ == '__main__':
#创建管道队列
queue = Manager().Queue()
#创建进程池对象
pool = Pool()
#准备好源文件夹
srcDir = 'D:/file'
#创建好目标文件夹
toDir = srcDir+'-复件'
os.makedirs(toDir)
list_files = os.listdir(srcDir)
for file in list_files:
#源文件
srcFile =srcDir +'/'+file
#目标文件
toFile =toDir +'/'+file
# 同步执行多任务
# pool.apply(copyFile,args=(srcFile,toFile,queue))
# 异步执行多任务
pool.apply_async(copyFile,args=(srcFile,toFile,queue)) # 有多少个文件,就开多少个进程完成复制
#先关闭进程池对象,防止进程池里边的进程被修改
pool.close()
#阻塞主进程
pool.join()
num = 0
total = len(list_files)
while num < total:
s = queue.get()
print('{0}复制完成'.format(s))
num += 1
print("复制完成度%.2f%%" % (num / total * 100))