进程池和回调函数

进程池和回调函数

一、管道

  • 管道是多进程之间通信的一种方式,它是不安全的。
  • 导入管道:
    • from multiprocessing import Pipe
    • con1, con2 = Pipe()
  • 单进程中使用管道:
    • 情形一:con1收数据,con2发数据
    • 情形二:con1发数据,con2收数据
  • 多进程中使用管道:
    • 情形一:父进程使用con1收,子进程使用con2发
    • 情形二:父进程使用con1发,子进程使用con2收
    • 情形三:父进程使用con2收,子进程使用con1发
    • 情形三:父进程使用con2发,子进程使用con1收

在管道中有一个著名的错误叫做EOFError,是指父进程关闭了发送端,子进程还继续接收数据,那么就会引发EOFError错误。

二、进程之间共享内存

  • 正常的多进程之间是没有建立通信的,每一个进程都有一个独立的内存,数据也不分享。

  • 多进程共享内存数据:

    • 导入模块: from multiprocessing import Manager
    • 实例化对象: m = Manager()
    • 选择传输的数据类型:num = m.dict/list/....(对应类型的数据)
    • 通过开启进程,把数据传递给子进程
    from multiprocessing import Manager, Process
    
    def calculate(num):
        num[3] += 1
        print('传输过来的数据计算后的结果为:%d' % num[3])
    
    if __name__ == '__main__':
        m = Manager()
        num = m.list([1,2,3,4])
        p = Process(target=calculate, args=(num,))
        p.start()
        p.join()
    
    

三、进程池

  • 在计算机中可以通过程序开启多个进程,但是受限于CPU,开启的进程数量是有上限的,不能无限制的开启进程,当开启很多个进程的时候单独是开启进程的过程就会消耗大量的资源,事件也很长,最佳的开启的多进程数量为CPU核数+1,例如计算机4核,那么最佳的开启多进程数量为5.
  • 在python程序中可以通过os模块来查询CPU核数:os.cpu_count()
  • 为什么要使用进程池:
    在实际的开发中,需要开启的任务有多有少,开启的进程数量时不固定的,尤其时面临需要开启大量的进程的时候,会大量消耗计算机中的资源,开发者人为的开启多进程很难把握开启几个进程合适,此时,需要用的进程池,在进程池中开启固定的进程数量来处理绝大部分任务,这样工作更加简便。
  • 进程池:
    一个池子,在池子里有固定数量的进程,没有任务的时候就呆在池子里,一旦有任务就由这些池子来处理任务,进程的数量由工作中的开发者根据情况给定。

需要特别注意的是:当有进程池的时候优先使用进程池管理任务,当进程池无法很好的解决任务的时候再自己开启多进程处理。

  • 进程池的使用格式:
    • 导入模块:from multiprocessing import Pool

    • 实例化进程池对象: p = Pool(os.cpu_count() + 1)

    • 进程池的开启方式:

      • 方式一:p.map(function, iterable)
        • map的返回结果是函数的返回值,当没有设定返回值的时候,默认返回为None
        • 第一个参数是函数名,第二个参数必须是可迭代的对象
        • 关闭进程池,不允许再向进程池安排任务:p.close()
        • 阻塞,同步处理:p.join(),这里的阻塞是让进程池处理完任务后再执行其他的任务
      from multiprocessing import Pool, Process
      import os
      import time
      
      def func(num):
          num += 1
          print(num)
          return num
      
      
      if __name__ == '__main__':
          p = Pool(os.cpu_count() + 1)
          # 第一种方式开启
          start_time = time.time()
          rest = p.map(func, [i for i in range(1, 101)])
          p.close()
          # 不允许向进程池中再添加任务
          p.join()
          # 等待进程池中所有的任务都完成之后再执行下边的进程,阻塞,同步
          end_time = time.time()
          print('进程池总共花费的时间为:%s' % (end_time - start_time))
          print('map的返回值为:%s' % rest)
          '''多进程计算'''
      
      • 方式二:p.apply()

      这种方式开启进程池后让进程帮我们同步的做事情

      • 方式三:p.apply_async()

      这种方式开启进程池后让进程帮我们异步的做事情,异步比同步效率更高,异步处理任务时,进程池中的所有进程都是守护进程,必须添加close()和join()

      • 方式二和方式三的用法基本一致:

        • 实例化进程池对象: p = Pool(os.cpu_count())
        • p.apply/apply_async(func, args=(传递给函数处理的参数,))
        • apply_asyn第三个参数是回调函数:callback=None

        同步返回的结果是函数返回的每一个直接的值,异步返回的结果是每一个值的对象对应的内存地址。异步获取的速度是同步的三倍,同步中虽然设定的是多个进程,但是执行的过程中却是一个一个进程轮流去处理任务,而异步在处理的过程中却是多个进程同时处理。

        • 在异步的处理中返回值是对象的内存地址,可以通过结果.get()获取到每一个对象对应的值,如果每一个对象的值获取都单独使用.get()方法,处理结果的速度和同步基本没有差别。可以在每一个对象的内存返回来后直接写入列表,在通过生成器循环的方式来.get()获取,这种速度比每一个单独.get()效率高一倍。
        • 同步处理时的进程是普通进程,不需要加close和join,异步处理的时候进程是守护进程,必须加上close和join。
      from multiprocessing import Pool, Process
      import os
      import time
      
      def func(num):
          num += 1
          # print(num)
          return num
      
      if __name__ == '__main__':
        p = Pool(os.cpu_count() + 1)
        '''同步'''
        start_time = time.time()
          for i in range(1001):
              res = p.apply(func, args=(i,))
              print(res)
          end_time = time.time()
          # print('同步花费的时间为:%s' % (end_time - start_time))
          print('异步花费的时间为:%s' % (end_time - start_time))
         '''异步'''
         start_time = time.time()
           obj_list = []
           for i in range(1001):
               result = p.apply_async(func, args=(i,))
               obj_list.append(result)
           p.close()
           p.join()
           [print(r.get()) for r in obj_list]
           end_time = time.time()
           print('异步获取结果的时间为:%s' % (end_time - start_time))
      
      

四、回调函数

回调函数是进程池中异步开启方法(apply_async())中的第三个参数callback=回调函数,回调函数的参数中的参数时子进程中任务函数的返回值,回调函数主要时对任务函数返回值进行再次的操作,例如存入数据库、再加工等。

需要特别注意的是:主进程调用回调函数,回调函数和主进程的pid是一样的,也就是是一个进程,子进程只负责把结果传递给回调函数。

  • 回调函数只有异步才有,同步没有
import requests
from multiprocessing import Pool
import logging
import time
import os


base_dir = os.path.dirname(__file__)

def func(url):
    '''任务函数'''
    res = requests.get(url)
    if res.status_code == 200:
        print(res.text)
        return url, res.text
    print('任务进程的pid:%s,父进程的pid:%s' % (os.getpid(), os.getppid()))

def call_back(sta):
    '''回调函数'''
    # print('回调函数中!', sta)
    url, text = sta
    print(url)
    with open(base_dir + 'file.txt', 'w') as f:
        f.write(text)

    print('回调函数的:pid%s,父进程的pid:%s' % (os.getpid(), os.getppid()))

if __name__ == '__main__':
    p = Pool(os.cpu_count() + 1)
    web_list = [
        'https://www.baidu.com',
        'https://www.toutiao.com',
    ]
    start_time = time.time()
    for web in web_list:
        p.apply_async(func, args=(web,), callback=call_back)
    p.close()
    p.join()
    end_time = time.time()
    print('异步爬虫的总时间为:%d' % (end_time - start_time))
    print('主进程的pid:%s,父进程的pid:%s' % (os.getpid(), os.getppid()))


posted @ 2020-03-08 20:53  大道至诚  阅读(238)  评论(0编辑  收藏  举报