python进阶学习之多进程
python学习 . 2020/04/16发布 . shanyonggang_web . 我要评论 . 234阅读

之前我们学习了多线程,但是python多线程由于GIL锁的存在,其不属于真正意义上的多线程,因此我们会使用到多进程,本文主要介绍多进程,参考官方文档:python多进程

多进程简介

多进程使用的multiprocessing包来产生进程,具有与threading模块相似API。 multiprocessing包同时提供本地和远程并发,使用子进程代替线程,有效避免 GIL 带来的影响。因此,multiprocessing模块允许程序员充分利用机器上的多核。可运行于 Unix 和 Windows 。

multiprocessing使用较多的是Process类,用法如下:

import multiprocessing
import os

def show(name):
    print('Children process %s.' % os.getpid())
    print('多进程设计{}'.format(name))

if __name__ == "__main__":
    print('Parent process %s.' % os.getpid())
    process = multiprocessing.Process(target=show,args=('hello',))
    process.start()
    process.join()

上述代码中需要使用if __name__ == '__main__',关于为什么 if __name__ == '__main__' 部分是必需的解释,主要是为了确保主模块可以被新启动的Python解释器安全导入而不会引发什么副作用(比如又启动了一个子进程),请参见 编程指导

与多线程类似,也可以使用继承的方式进行,如下:

import multiprocessing
import os

class ShowName(multiprocessing.Process):
    def __init__(self,name):
        super(ShowName,self).__init__()
        self.name = name

    def run(self):
        print('Children process %s.' % os.getpid())
        print('多进程设计{}'.format(self.name))

if __name__ == "__main__":
    print('Parent process %s.' % os.getpid())
    process = ShowName('hello')
    process.start()
    process.join()

进程间通信

进程之前的通信,主要两种方式“1、使用队列形式;2、使用管道形式

队列,与多线程中的queue.Queue类似,多进程使用multiprocessing.Queue,用法如下:

import multiprocessing

def test(q):
    q.put([12,'hello','world','89'])

if __name__ == "__main__":
    
    q = multiprocessing.Queue()
    mul = multiprocessing.Process(target=test,args=(q,))
    mul.start()
    print(q.get())
    mul.join()

管道,返回一个由管道连接的连接对象,默认情况下是双工(双向)。用法如下:

from multiprocessing import Process, Pipe

def f(conn):
    conn.send([12,'hello','world','89'])
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())   # prints "[42, None, 'hello']"
    p.join()

每个连接对象都有 send() 和 recv() 方法(相互之间的)。

进程之间的同步,与多线程类似,多进程中也是用Lock锁的形式,如下:

from multiprocessing import Process, Lock

def f(l, i):
    l.acquire()
    try:
        print('hello world', i)
    finally:
        l.release()

if __name__ == '__main__':
    lock = Lock()

    for num in range(10):
        Process(target=f, args=(lock, num)).start()

进程池,使用multiprocessing.Pool,如下:

import multiprocessing
import os
import time
import random

def f(x):
    print('Run task %s (%s)...' % (x, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('Task %s runs %0.2f seconds.' % (x, (end - start)))

if __name__ == "__main__":

    pool = multiprocessing.Pool(processes=4)

    for i in range(7):
        pool.apply_async(f, (i,))      # runs in *only* one process
    print('Waiting for all subprocesses done...')
    pool.close()
    pool.join()
    print('All subprocesses done.')

杂项:

  • multiprocessing.active_children()  返回当前进程存活的子进程的列表。
  • multiprocessing.cpu_count()  返回系统的CPU数量
  • multiprocessing.current_process()  返回与当前进程相对应的Process对象。
  • multiprocessing.current_process()  返回父进程的Process对象。

实战案例

本次依然使用爬虫例子,通多线程类似,爬取表情包图片,具体代码如下:

# 使用生产者与消费者模式多线程下载表情包
import multiprocessing
import requests
import time
from lxml import etree
from urllib import request
import os
import re

class Producer(multiprocessing.Process):

    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'
    }

    def __init__(self,page_queue,img_queue,*agrs,**kwargs):
        super(Producer,self).__init__(*agrs,**kwargs)
        self.page_queue = page_queue
        self.img_queue = img_queue

    def run(self):
        print('进程{}正在运行中......'.format(multiprocessing.current_process().name))
        while True:
            if self.page_queue.empty():
                break
            url = self.page_queue.get()
            self.parse_page(url)

    def parse_page(self,url):
        response = requests.get(url,headers=self.headers)
        html = etree.HTML(response.text)
        imgs = html.xpath("//div[@class='page-content text-center']//a//img")
        for img in imgs:
            if img.get('class') == 'gif':
                continue
            # 获取图片地址
            img_url = img.xpath(".//@data-original")[0]
            suffix = os.path.splitext(img_url)[1]
            # 获取名称
            alt = img.xpath(".//@alt")[0]
            alt = re.sub(r'[,。??,/\\·]','',alt)
            img_name = alt + suffix
            self.img_queue.put((img_url,img_name))

class Consumer(multiprocessing.Process):
    def __init__(self,page_queue,img_queue,*agrs,**kwargs):
        super(Consumer,self).__init__(*agrs,**kwargs)
        self.page_queue = page_queue
        self.img_queue = img_queue

    def run(self):
        print('线程{}正在运行中......'.format(multiprocessing.current_process().name))
        while True:
            if self.img_queue.empty():
                if self.page_queue.empty():
                    return
            img = self.img_queue.get(block=True)
            url,filename = img
            request.urlretrieve(url,'F:/0_个人学习/0_网络爬虫/0_开发实战/多进程、多线程练习/多进程/图片包/'+filename)
            print(filename+'  下载完成!')

def main():
    page_queue = multiprocessing.Queue()
    img_queue = multiprocessing.Queue()
    p = multiprocessing.Pool(processes=4)
    # 本次仅爬去30页
    for x in range(1,31):
        url = 'https://www.doutula.com/photo/list/?page={}'.format(x)
        print('添加到页面队列里!!!')
        page_queue.put(url)
    for x in range(6):
        t = Producer(page_queue,img_queue)
        t.start()
    for x in range(6):
        t = Consumer(page_queue,img_queue)
        t.start()
if __name__ == "__main__":
    main() 

总结

  1. 在Unix/Linux下,可以使用fork()调用实现多进程。
  2. 要实现跨平台的多进程,可以使用multiprocessing模块。
  3. 进程间通信是通过QueuePipes等实现的。
  4. 多线程:密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。 
  5. 缺陷:多个进程之间通信成本高,切换开销大。

  • 有疑问请在下方评论区留言,我会尽快回复。
  • Email私信我: 511248513@qq.com 或添加博主 微信
本文作者:shanyonggang_web
发布时间:2020年4月16日 10:43
许可协议: 署名-非商业性使用 4.0 国际许可协议
知识共享许可协议     转载请保留原文链接及作者
正在加载今日诗词....
您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请狠狠点击下面的


登录 后回复

当前暂无评论,点击登录来做第一个吃螃蟹的人吧!