文章目录

  • 多进程
  • 1. multiprocessing创建子进程
  • 2. 自定义类创建子进程
  • 3. 进程池
  • 4. 多进程复习(以外补充的视频)
  • 5. 使用multiprocess.Queue实现进程间通信
  • 多进程练习:


多进程

  • 简介
    任务可以理解为程序,多个程序同时执行 比如:边听歌,边看小说 边写代码,边听相声 单核电脑实现多任务: 调度算法: 时间片轮转(翻牌) 调用优先级(等级) 并发: 3个任务,2个cpu,轮番调度 并行: 4个cpu,3个任务
  • 就好比QQ就是主进程,QQ开的对话窗口就是子进程,如果开了三个对话窗口,就好比开了三个子进程。

1. multiprocessing创建子进程

  1. Process(target, name, args)参数介绍
  • target:标识调用对象,即子进程要执行的任务
  • args:标识调用对象的位置参数元组,args=(1,)
  • name:为子进程的名称
  1. Process 类常用方法:
  • process.start():启动进程,并调用该子进程中的p.run()
  • process.run():进程启动时运行的方法,正式它去调用target指定的函数,我们定义类的类中一定要实现的该方法
  • process.terminate():(了解)前置终止进程p,不会进行任何清理操作
  • process.is_alive():如果p任然运行,返回True,用来判断进程是否还在运行
  • process.join([timeout]):主进程等待子线程process终止 ,timeout是可选的超时时间
  1. 全局变量在多个进程中不共享:进程之间的数据是独立的,默认情况下互不影响。
  2. 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. 进程池

  1. 进程池:创建多个进程。
  2. 如果是上百个甚至上千个目标,手动的去创建进程工作量巨大,此时就可以用到multiprocess模块中的 Pool。初始化Pool时,可以指定一个 最大进程数
  3. 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个任务。
  1. python处理多任务的方式:
  1. 多进程(计算密集型的)
  2. 多线程(IO密集型的)
  1. 守护进程的问题:
  1. daemon:设置为守护进程后,主进程结束后,所有的子线程跟着结束(否则子进程会自动执行==默认)。
  2. join:阻塞队列中的进程,直到当前进程完成,队列中的进程才会继续
  1. 常用方法:
  • 创建进程池: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实现进程间通信

  1. Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据,我们以Queue做示例。
  2. Queue就相当于容器,遵循“先进先出”,
  3. 常用方法:
  • 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
  1. 例子:模拟读写数据
"""
进程间的通信: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))