python之路_并发编程之多进程3

一、生产者消费模型补充

  总结:

    ---生产者消费者模型程序中两种角色:①负责生产数据(生产者);②负责处理数据(消费者)

    ---生产者消费者模型的作用:平衡生产者与消费者之间的速度差。

    ---实现方式:生产者——>队列——>消费者

  如上篇博客内容关于生产消费模型内容,在生产者生产数据的过程结束后,即使消费者已将数据完全获取,消费者程序也不能结束,需由主进程或者生产者在结束生产程序后发送给消费者结束口令,消费者程序才会结束。但是如果出现多个消费者和多个生产者,这种情况又该如何解决?方法如下两种:

1、根据消费者数量传送结束信号(low)

from multiprocessing import Process
from multiprocessing import Queue

import time

def producer(q,food):
    for i in range(5):
        q.put('%s-%s'%(i,food))
        print('生成了1%s'%food)
        time.sleep(1)
    q.put(None)
    q.put(None)


def consumer(q,name):
    while 1:
        food=q.get()
        if food==None:break
        print('%s吃掉了%s'%(name,food))


if __name__ == '__main__':
    q=Queue()
    p=Process(target=producer,args=(q,'肉'))
    p.start()
    p2 = Process(target=producer, args=(q, '蔬菜'))
    p2.start()

    c=Process(target=consumer,args=(q,'alex'))
    # c.daemon=True
    c.start()
    c2=Process(target=consumer,args=(q,'egon'))
    # c2.daemon=True
    c2.start()

 

 

2、JoinableQueue队列机制

 JoinableQueue与Queue队列基本相似,但前者队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。Queue实例的对象具有的方法JoinableQueue同样具有,除此JoinableQueue还具有如下方法:

  ①q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止

  ②q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常

from multiprocessing import JoinableQueue
from multiprocessing import Process
import time

def producer(q,food):
    for i in range(5):
        q.put('%s-%s'%(i,food))
        print('生成了1%s'%food)
        time.sleep(1)
    q.join()  # 卡住,等待q 里的数据被取完


def consumer(q,name):
    while 1:
        food=q.get()
        if food==None:break
        print('%s吃掉了%s'%(name,food))
        q.task_done()


if __name__ == '__main__':
    q=JoinableQueue()
    p=Process(target=producer,args=(q,'肉'))
    p.start()
    p2 = Process(target=producer, args=(q, '蔬菜'))
    p2.start()

    c=Process(target=consumer,args=(q,'alex'))
    c.daemon=True
    c.start()
    c2=Process(target=consumer,args=(q,'egon'))
    c2.daemon=True
    c2.start()

    p.join()
    p2.join()

 

二、共享内存(Manager)

  展望未来,基于消息传递的并发编程是大势所趋,即便是使用线程,推荐做法也是将程序设计为大量独立的线程集合,通过消息队列交换数据。这样极大地减少了对使用锁定和其他同步手段的需求,还可以扩展到分布式系统中,进程间通信应该尽量避免使用本节所讲的共享数据的方式。

from multiprocessing import Process,Lock,Manager
def work(d,lock):
    '''加锁是保证数据被修改时是逐一进行的,避免多个进程同时拿到一个起始数据,导致数据混乱'''
    with lock:
        d['count']-=1
if __name__=='__main__':
    lock=Lock()
    m=Manager()
    d=m.dict({'count':100})                  #也可以对其他数据类型进行性共享,如list
    _p=[]
    for i in range(10):
        p=Process(target=work,args=(d,lock))
        p.start()
        _p.append(p)
    for p in _p:
        p.join()
    print(d)                                 #{'count': 90}

 

三、进程池(Pool)

  多进程是实现并发的主要手段之一,但是通常会有如下问题:a.很明显需要并发执行的任务通常要远大于核数;b.一个操作系统不可能无限开启进程,通常有几个核就开几个进程;c.进程开启过多,效率反而会下降(开启进程是需要占用系统资源的,而且开启多余核数目的进程也无法做到并行)。

  Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程。执行任务的进程数始终未进程池中指定的那几个。

 

1.map

# 进程池
# 造一个池子 放四个进程来用来完成工作
# 发任务,
# 循环使用池子中进程来完成任务

# 进程池 已经被实现了
from multiprocessing import Pool
from multiprocessing import Process
import os,time,random
def func(i):
    pass
    # print(i,os.getpid())
    # time.sleep(random.randint(1,3))

if __name__ =='__main__':
    p=Pool(5)               # 创建了五个进程
    start=time.time()
    p.map(func,range(200))  # target = func   args = next(iterable)   # [(1,2,3),1,2,3,4]
    p.close() # 是不运行再向进程池中添加任务了
    p.join()
    print(time.time()-start)
    start=time.time()
    l=[]
    for i in range(200):   # 创建了100个进程
        p=Process(target=func,args=(i,))
        p.start()
        l.append(p)
    for i in l:
        i.join()
    print(time.time()-start)

 

2、同步调用(apply)

  同步调用:提交完任务后,在原地等待任务结束,一旦结束可以立刻拿到结果。

from multiprocessing import Pool
import time,os
import random
def work(n):
    print('%s is working' %(os.getpid()))
    time.sleep(random.randint(1,3))
    return n**2


if __name__=='__main__':
    p=Pool(4)                                   #从无到有开启4个进程,而且一直是这4个进程
for i in range(10):
    res=p.apply(work,args=(i,))                 #等进程执行完,并能得到结果,然后才开启下一个进程,相当于串行 
    print(res)

 

3、异步调用(apply_async) 

  异步调用:提交完任务后,不会在原地等待任务结束,会继续提交下一次任务,等到所有任务都结束后,才能get结果。

import os,time
from multiprocessing import  Pool,Process

def func(i):
    time.sleep(1)
    i+=1
    # print(i)
    return i+1
if __name__ =='__main__':
    p=Pool(5)
    l=[]
    for i in range(20):
        # p.apply(func,args=(i,))       # apply 是同步提交任务的机制
        res=p.apply_async(func,args=(i,))   # apply_async 是异步提交任务的机制
        # print(res.get())   # 阻塞住,等待任务结果
        l.append(res)
    p.close()   # close 必须加载join之前,不允许 再添加任务了
    p.join()  # 等待子进程结束再往下执行
    [print(i.get()) for i in l]

 

四、回调函数

  进程池执行完一个获得数据的进程,即刻要求通知主进程拿去解析数据。主进程调用一个函数去处理,这个函数便被称为回调函数,要求进程池进程的结果为回调函数的参数。

  爬虫实例:

from multiprocessing import Pool,Process
import requests
import os
import time,random
def get(url):
    print('%s GET %s' %(os.getpid(),url))
    response=requests.get(url)
    time.sleep(random.randint(1,3))
    if response.status_code == 200:
        print('%s DONE %s' % (os.getpid(), url))
        return {'url':url,'text':response.text}

def parse(dic):
    print('%s PARSE %s' %(os.getpid(),dic['url']))
    time.sleep(1)
    res='%s:%s\n' %(dic['url'],len(dic['text']))
    with open('db.txt','a') as f:
        f.write(res)

if __name__ == '__main__':
    urls=[
        'https://www.baidu.com',
        'https://www.python.org',
        'https://www.openstack.org',
        'https://help.github.com/',
        'http://www.sina.com.cn/'
    ]
    p=Pool(2)
    for url in urls:
        p.apply_async(get,args=(url,),callback=parse) #主进程负责干回调函数的活
    p.close()
    p.join()

 

 

 

posted @ 2018-12-01 16:56  时间的侵蚀  阅读(69)  评论(0)    收藏  举报