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

一、守护进程

  主进程创建守护进程,守护进程的主要的特征为:①守护进程会在主进程代码执行结束时立即终止;②守护进程内无法继续再开子进程,否则会抛出异常。

实例:

from multiprocessing import Process
import time
def foo():
    print('starting123')
    time.sleep(1)
    print('endig123')
def bar():
    print('starting456')
    time.sleep(3)
    print('ending456')
if __name__=='__main__':
    p1=Process(target=foo)
    p2=Process(target=bar)
    p1.daemon=True                       #必须在start()之前开守护进程
    p1.start()                           #向操作系统发出开启p1子进程请求
    p2.start()                           #向操作系统发出开启p2子进程请求
    print('main')                        #主进程最后一行代码执行打印完后,立即终止p1子进程的执行

  注:打印最后一行主进程代码结束,则守护进程p1应该被终止,可能会有p1任务执行的打印信息‘start123’,因为主进程打印main-时,p1也执行了,但是随即被终止。

二、互斥锁 

  进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理(即局部实行串行)。

模拟抢票实例:

#文件db的内容为:{"count":1}
#注意一定要用双引号,不然json无法识别
from multiprocessing import Process,Lock
import json
import time
def search(n):
    dic=json.load(open('db'))
    print('<%s> 剩余票数%s' %(n,dic['count']))
def get(n):
    dic=json.load(open('db'))
    if dic['count']>0:
        dic['count']-=1
        time.sleep(1)           #模拟网络延迟
        json.dump(dic,open('db','w'))
        print('<%s> 购票成功' %n)
    else:
        print('剩余票数为%s,购票失败' %dic['count'])
def task(n,lock):
    search(n)                   #10个人都可以并发的查询剩余票数
    lock.acquire()              #获得锁
    get(n)                      #通过锁,查询到结果的10人通过竞争逐一买票。前一个释放锁后后一个才可以进入,即串行
    lock.release()              #释放锁
    #互斥锁的另一种形式
    # with lock:
    #     get(n)
if __name__=='__main__':
    lock=Lock()
    for i in range(10):         #模拟10个抢票的人
        p=Process(target=task,args=(i,lock))
        p.start()

  注:加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。缺点:①共享数据基于文件,而文件是硬盘上的数据,导致效率低;②需要自己加锁处理。

三、进程间通信机制(IPC)

  基于互斥锁以上两种缺点,multiprocessing模块为我们提供了基于消息通信IPC机制:队列和管道。队列和管道都是将数据存放于内存中;队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。

1、队列(推荐)

(1)队列相关知识

  队列创建介绍:

from multiprocessing import Queue      #引入Queue类
q=Queue(n)                             #实例化,参数n代表队列中最大允许存放数,省略则无限制

  常见使用方法:

q.put()                                #用于插入数据到队列
q.get()                                #用于从队列读取并删除一个数据
q.put_nowait()                         #当队列存在数据已超过最大限制数,则抛出Queue.full异常
q.get_nowait()                         #当队列中已经不存在可取数据时,则抛出Queue.empty异常

  实例:

from multiprocessing import Queue
q=Queue(3)
q.put({'a':1})
q.put('bbbb')
q.put((3,2,1))
# q.put_nowait(1111111)  #queue.Full

print(q.get())
print(q.get())
print(q.get())
# print(q.get_nowait())  #queue.Empty

(2)生产消费者模型

  生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

  实例1:

from multiprocessing import Queue,Process
import time,random
def producer(name,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='泔水%s' %i
        q.put(res)
        print('厨师 %s 生产了 %s' %(name,res))
def consumer(name,q):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print('%s 吃了 %s' %(name,res))

if __name__ == '__main__':
    q=Queue()
    p1=Process(target=producer,args=('egon',q))
    c1=Process(target=consumer,args=('alex',q))

    p1.start()
    c1.start()

  此时的问题是主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环。

  实例 2:

from multiprocessing import Queue,Process
import time,random
def producer(name,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='泔水%s' %i
        q.put(res)
        print('厨师 %s 生产了 %s' %(name,res))
def consumer(name,q):
    while True:
        res=q.get()
        if res is None:break
        time.sleep(random.randint(1,3))
        print('%s 吃了 %s' %(name,res))

if __name__ == '__main__':
    q=Queue()
    p1=Process(target=producer,args=('egon',q))
    c1=Process(target=consumer,args=('alex',q))

    p1.start()
    c1.start()
    p1.join()
    q.put(None)

注意:以上发送可以放在生产函数中循环完进行发送,当然也可以如上放在主进程中进行发送,但是前提是必须等生产子进程结束才可以。

2、管道

原文地址:https://www.cnblogs.com/seven-007/p/7651676.html