34. 锁,队列,信号量,事件

昨日内容回顾:

 

1 创建进程的两种方法

直接使用from multiprocessing import Process

自定义一个类,继承Process,重写run方法,如果需要传参,重写init,并调用super执行父类的init

2 两种传参方式:

Args = (1,)元组

Kwargs = {‘n’:1,}

3 验证进程之间是空间隔离的

全局变量

4 join等待子进程结束,然后继续往下执行,

 

5 验证了一下并发的执行时间

 

6 for循环在多进程中的应用

 

7 terminate 关闭进程,但是他只是给操作系统发送一个关闭进程的信号,实际操作是操作系统关闭的.

8 is_alive  查看进程是否还活着

9 僵尸进程和孤儿进程

10 守护进程

P1.Daemon = True

11 子进程不能input

 

12 __name__ == ‘__main__’ windows

 

今日内容:

进程同步:

    #同步效率低,但是保证了数据安全  重点

信号量 #  

事件  #

 

进程间通信IPC

队列   #重点

生产者消费者模型

JoinableQueue

 

import json
import time
import random
from multiprocessing import Process,Lock

def get_ticket(i,ticket_lock):
    print('我们都准备好抢票了')
    time.sleep(1)
    ticket_lock.acquire() #这里有个门。只有一个人能抢到钥匙
    with open('ticket','r')as f:
        last_ticket_info = json.load(f)
        last_ticket = last_ticket_info['account']
        if last_ticket > 0:
            time.sleep(random.random())
            last_ticket = last_ticket - 1
            last_ticket_info['account'] = last_ticket
            with open('ticket','w')as f:
                json.dump(last_ticket_info,f)
            print('%s号抢到票了'%i)
        else:
            print('没票了,%s号傻逼'%i)
if __name__ == '__main__':
    ticket_Lock = Lock()
    for i in range(10):
        p = Process(target=  get_ticket,args = (i,ticket_Lock))
        p.start()
#加锁可以保证多个进程修改同一块数据时,同一时间只有一个任务可以修改,即串
# 行的修改牺牲了速度却保证了数据安全
# 虽然可以用文件共享数据实现进程间的通信,问题是
#1.效率低(共享数据基于文件,而文件是硬盘上的数据)
#2.需要自己加锁处理

'''mutiprocessing模块为我们提供了兼顾1.效率高(多个进程共享一块内存的数据)2.帮
我们处理好锁问题。基于消息的ipc通信机制,队列和管道
队列和管道都是将数据存放于内存中,队列又是基于(管道+锁)实现的,可以让我们从复杂的
锁问题中脱离出来。
应该尽量避免使用数据共享,尽可能使用消息传递和队列,避免处理复杂问题的同步和锁问题
往往可以获得更好的扩展性

IPC通信机制:IPC是intent—Process—Communication的缩写,含义为进程间通信或者
跨进程通信,是指两个进程之间进行数据交换的过程,ipc不是某个系统独有的,任何一个操作
系统都需要有相对应的IPc机制
比如Windows上可以通过剪贴板、管道和邮槽等来进行进程间通信,而Linux上可以通过命名共享内容、信号量等来进行进程间通信。Android它也有自己的进程间通信方式,Android建构在Linux基础上,继承了一
部分Linux的通信方式。


'''

 

信号量

import time
import random
from multiprocessing import Semaphore,Process
def dbj(i,s):
    s.acquire()
    print('%s号男主角来洗脚'%i)
    print('----')
    # time.sleep(random.randrange(3,6))
    time.sleep(1)
    s.release()
if __name__ == '__main__':
    s = Semaphore(4)#创建一个计数器,每次acquire就减1,直到减到0,那
    # 么上面的任务只有4个在同时异步的执行,后面的进程需要等待.
    for i in range(10):
        p1 = Process(target = dbj,args = (i,s,))
        p1.start()







#互斥锁同时只允许一个现成更改数据,而信号量Semaphore是同时允许一定数量的
# 线程更改数据,
#信号量同步于内部计数器,每调用一次acquire(),计数器减一,每调用一次release()
#计数器加一,当计数器为0时,acquire()会被阻塞,

 

队列

进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的。队列就像一个特殊的列表,但是可以设置固定长度,并且从前面插入数据,从后面取出数据,先进先出。

Queue([maxsize]) 创建共享的进程队列。
参数 :maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。
底层队列使用管道和锁实现。
'''队列彼此之间互相隔离,要实现进程间的相互通信,multiprocessing模块支持两种形式,
队列和管道,这两种消息都是使用消息传递的,队列就像一个特殊的列表,但是可以设定固定
长度,而且从前边插入数据,从后边取出数据先进先出'''
# Queue([maxsize]) 创建共享的进程队列
# 参数:maxsize 是队列中允许的最大项数,省略不写,则无大小限制
#底层队列使用管道和锁实现

from multiprocessing import Process,Queue
q = Queue(3)#创建一个队列对象,队列长度为3

q.put(1)
q.put(2)
q.put(3)#往队列中添加数据
#q.put(4)  #队列已经满了,程序就会停在这里,等待数据被别人取走,再将数据放入队列中
#如果队列中的数据一直不被取走,程序就会永远停在这里

try:
    q.put_nowait(4)#往里面放4,可以使用nowait,如果队列满了不会阻塞,但是会因为队列满了而报错
except:
    print('队列已经满了')
    #使用try语句来处理这个错误,这样程序就不会一直阻塞下去,但是会丢掉这个消息

#因此,再放入数据之前,可以先看一下队列的状态,如果已经满了,就不继续put了
print(q.full()) #满了,返回true,不满返回False

print(q.get())#取出数据
print(q.get())
print(q.qsize())
print(q.empty())
print(q.get())
# print(q.get())#同put一样,空了继续取就会出现阻塞
try:
    q.get_nowait()#可以使用nowait,如果队列空了不会阻塞,但是会因为没取到值而报错
except:
    print('队列已经空了')
    # 使用try语句来处理这个错误,这样程序就不会一直阻塞下去
print(q.empty())
print(q.qsize())

队列实现进程间的通信

import time
from multiprocessing import Process,Queue

def boy(q):
    q.put('姑娘,约吗')   #调用主函数传过来的进程参数,put函数向队列中添加一条数据
    print(q.qsize())  #如果主函数延迟时间,查看的qsize只有一条
def girl(q):
    print('来自boy的信息',q.get())
    print('来自主函数老大的信息',q.get())
    print('啦啦啦')
if __name__ == '__main__':
    q = Queue()  #创建一个队列对象
    boy_p = Process(target=boy,args = (q,))   #创建进程
    girl_p = Process(target=girl,args= (q,))   #创建进程
    boy_p.start()
    girl_p.start()
    time.sleep(1)
    q.put('好好学习,约个锤子')

#队列是进程安全的,同一时间只能一个进程拿到队列中的一个数据,拿到了一个数据,这个数据别人
# 就拿不到了

事件

#python线程的事件用于主线程控制其它线程的执行,事件主要提供了三个方法
#set,wait,clear
    #事件处理的机制:全局定义了一个'Flag'.如果flag的值为False,那么当程序执行event.wait
# 方法时就会阻塞,如果flag的值为True,那么event.wait方法时便不再阻塞
#clear 将flag设置为false
# set 将flag设置为True

from multiprocessing import Process,Semaphore,Event
import time,random

e = Event()   #创建一个事件对象
print(e.is_set())#is_set  查看当前事件的状态,默认为False,可通过set方法
# 改为true
e.set()  #将is_set()状态改为True
print(e.is_set()) #查看
e.clear()   #将is_set()状态改为False
print(e.is_set())#查看结果
e.wait()
print('give me!!!')#根据is_set()的状态结果来确定是否在这里阻塞住,false阻塞
# true不阻塞

#set,clear修改事件的状态
#is_set()  用来查看一个事件的状态
#wait 依据事件的状态来确定是否阻塞住

通过event来完成红绿灯

import time
import random
from multiprocessing import Process,Event

#模拟红绿灯执行状态的函数
def traffic_lights(e):
    while 1:
        print('红灯啦')
        time.sleep(5)
        e.set()  #将值设为true,模拟绿灯
        print('绿灯亮')
        time.sleep(3)
        e.clear()

def car(i,e):
    if not e.is_set():  #进程刚开启,is_set()的值是false,模拟信号灯为红色
        print('我们在等待')
        e.wait()#阻塞,等待is_set的值变为True,模拟信号灯为绿色
        print('走你')
    else:#如果直接是true,说明是绿的,不用等,直接走
        print('可以走了')
if __name__ == '__main__':
    e = Event()
    hld = Process(target = traffic_lights,args = (e,))
    hld.start()
    while 1:
        time.sleep(0.5)
        for i in range(3):
            time.sleep(random.randrange(1, 3))
            p1 = Process(target = car,args = (i,e))
            p1.start()

生产者消费者模型

#生产者消费者模型
#就是加了个缓冲区
# 版本一
# import time
# from multiprocessing import Process,Queue
# def producer(q):
#     for i in range(1,11):
#         time.sleep(1)
#         print('生产了包子%s号'%i)
#         q.put(i)
#
# def consumer(q):
#     while 1:
#         time.sleep(2)
#         print('消费者吃了%s号包子'% q.get())
# if __name__ == '__main__':
#     q = Queue(20)
#     pro_p = Process(target=producer,args =(q,))
#     pro_p.start()
#     con_p = Process(target=consumer,args=(q,))
#     con_p.start()
#出现的问题就是没有包子了还在等
# import time
# from multiprocessing import Process,Queue
# def producer(q):
#     for i in range(1,11):
#         time.sleep(1)
#         print('生产了包子%s号'%i)
#         q.put(i)
#
# def consumer(q):
#     while 1:
#         time.sleep(0.5)
#         try:
#             s = q.get()
#             print('消费者吃了%s号包子'%s)
#         except:
#             break
#版本二
#
#
# if __name__ == '__main__':
#     q = Queue(20)
#     pro_p = Process(target=producer,args =(q,))
#     pro_p.start()
#     con_p = Process(target=consumer,args=(q,))
#     con_p.start()

#
# import time
# from multiprocessing import Process,Queue
# def producer(q):
#     for i in range(1,11):
#         time.sleep(1)
#         print('生产了包子%s号'%i)
#         q.put(i)
#     q.put(None)#针对第三个版本的消费者
#
# def consumer(q):
#     while 1:
#         time.sleep(2)
#         s = q.get()
#         if s == None:
#             break
#         else:
#             print('消费者吃了%s号包子'%s)
#
#
#
#
# if __name__ == '__main__':
#     q = Queue(20)
#     pro_p = Process(target=producer,args =(q,))
#     pro_p.start()
#     con_p = Process(target=consumer,args=(q,))
#     con_p.start()

'''通过上面基于队列的生产者消费者代码示例,我们发现一个问题:主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。

    解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环'''
# 注意:结束信号None,不一定要由生产者发,主进程里同样可以发,但主进程需要等生产者结束后才应该发送该信号

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

    为什么要使用生产者和消费者模式

      在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

    什么是生产者消费者模式

      生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力,并且我可以根据生产速度和消费速度来均衡一下多少个生产者可以为多少个消费者提供足够的服务,就可以开多进程等等,而这些进程都是到阻塞队列或者说是缓冲区中去获取或者添加数据。

    通俗的解释:看图说话。。背景有点乱,等我更新~~

    

 

#生产者消费者模型总结

    #程序中有两类角色
        一类负责生产数据(生产者)
        一类负责处理数据(消费者)
        
    #引入生产者消费者模型为了解决的问题是:
        平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度
        
    #如何实现:
        生产者<-->队列<——>消费者
    #生产者消费者模型实现类程序的解耦和
复制代码

 

    通过上面基于队列的生产者消费者代码示例,我们发现一个问题:主进程永远不会结束,原因是:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步。

    解决方式无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环

 生产者消费者主进程发送结束信号

import time
from multiprocessing import Process,Queue
def producer(q):
    for i in range(1,11):
        time.sleep(1)
        print('生产了包子%s号'%i)
        q.put(i)

def consumer(q):
    while 1:
        time.sleep(2)
        s = q.get()
        if s == None:
            break
        else:
            print('消费者吃了%s号包子' % q.get())
if __name__ =='__main__':
    q = Queue(20)

    #生产者进程
    pro_p= Process(target = producer,args = (q,))
    pro_p.start()

    #消费者进程
    con_p= Process(target = consumer,args = (q,))
    con_p.start()

    pro_p.join()#等待生产者进程结束
    q.put(None)




if __name__ == '__main__':
    q = Queue(20)
    #生产者进程
    pro_p = Process(target=producer,args =(q,))
    pro_p.start()
    #消费者进程
    con_p = Process(target=consumer,args=(q,))
    con_p.start()
    pro_p.join()

    q.put(None)

JoinableQueue([maxsize]) 

复制代码
#JoinableQueue([maxsize]):这就像是一个Queue对象,但队列允许项目的使用者通知生成者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。

   #参数介绍:
    maxsize是队列中允许最大项数,省略则无大小限制。    
  #方法介绍:
    JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
    q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
    q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止,也就是队列中的数据全部被get拿走了。
复制代码

 

import time
from multiprocessing import Process,JoinableQueue

def producer(q):
    for i in range(1,11):
        time.sleep(0.5)
        print('生产了%s号包子'%i)
        q.put(i)
    q.join()   #生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理
    print('在这里等你')
def consumer(q):
    while 1:
        time.sleep(1)
        s = q.get()
        print('消费者吃了%s号包子'%s)
        q.task_done()#给消费者发了一个结束任务的信号

if __name__ == '__main__':
    #通过队列来模拟缓冲区
    q = JoinableQueue(20)
    pro_p = Process(target = producer,args =(q,))
    pro_p.start()
    con_p = Process(target=consumer, args=(q,))
    con_p.daemon = True
    # 如果不加守护,那么主进程结束不了,但是加了守护之后,必须确保生产者
    # 的内容生产完并且被处理完了,所有必须还要在主进程给生产者设置join,
    # 才能确保生产者生产的任务被执行完了,并且能够确保守护进程在所有任务
    # 执行完成之后才随着主进程的结束而结束。
    con_p.start()

    pro_p.join()
    # 我要确保你的生产者进程结束了,生产者进程的结束标志着你生产的所有的
    # 人任务都已经被处理完了
    print('主进程结束')

 

posted on 2018-11-06 23:11  小王子QAQ  阅读(44)  评论(0)    收藏  举报