(multiprocess.Lock)锁和(multiprocess.Queue)队列

进程同步(锁)

现在有这么情况,我们模仿了一个抢票软件,文件里有十张票,一共有二十个人去抢,抢走一张就少一张,二十个人我们为了达到并发的效果,显然是要以多进程来实现的,那么这时候就有一个问题,好几个人同时看到的都是还剩下10张票,每个人都抢了一张,结果只是剩下19。问题就是这个,应该是某个人在抢的时候,其他人无法抢才对。所以这时候我们就需要给这个步骤上锁,只要有进程在执行的时候,别的进程就无法执行这段代码,只能等待。

这时候我们需要在multiprocessing中导入一个Lock类。

#文件db的内容为:{"count":1}
#注意一定要用双引号,不然json无法识别
from multiprocessing import Process,Lock
import time,json,random
def search():
    dic=json.load(open('db.txt'))
    print('33[43m剩余票数%s33[0m' %dic['count'])

def get():
    dic=json.load(open('db.txt'))
    time.sleep(0.1) #模拟读数据的网络延迟
    if dic['count'] >0:
        dic['count']-=1
        time.sleep(0.2) #模拟写数据的网络延迟
        json.dump(dic,open('db.txt','w'))
        print('33[43m购票成功33[0m')

def task(lock):
    search()
    lock.acquire()
    get()
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    for i in range(100): #模拟并发100个客户端抢票
        p=Process(target=task,args=(lock,))
        p.start()

加锁:购票行为由并发变成了串行,牺牲了运行效率,但保证了数据安全
进程锁 是把锁住的代码变成了串行
join 是把所有的子进程变成了串行


 

在我们主进程的代码里面生成一个锁(只能在这里,才是一个锁,在上面的方法里的话就是生成多个锁了),然后把这个锁的对象当做参数传进去。lock.acquire()就是开启锁,只有一个进程能进去执行,下一个进程想进去必须要等lock.release()释放才能进去。这就很好的实现了这段代码只有一个进程才能跑,操作文件同时只能一个进程。

加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全

那我速度降低了我肯定不乐意啊,有没有什么方法能够两全其美呢?还真有。

那就是队列和管道

队列≈管道+锁

队列

举个栗子。

凉轻松去一家包子店吃包子,厨师只管自己做包子,做完一个就放一个进大盘子里,凉轻松只管从大盘子里拿包子,没了就等着。这个大盘子就是所谓的队列。

创建方式

Queue([maxsize])

maxsize是队列中允许最大项数,省略则无大小限制。

1 q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.Full异常。
2 q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
  
3 q.get_nowait():同q.get(False),就是说不等,有就拿,没有就砸店
4 q.put_nowait():同q.put(False),同上(厨师:我为什么要砸店?)
 
5 q.empty():调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。
6 q.full():调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。
7 q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样

栗子

from multiprocessing import Process,Queue
import time,random

def producer(q,name,food):
    '''生产者'''
    for i in range(3):
        print(f'{name}生产了{food}{i}')
        time.sleep(random.randint(1, 3))
        res = f'{food}{i}'
        q.put(res)
    # q.put(None)

def consumer(q,name):
    '''消费者'''
    while True:
        res = q.get(timeout=5)
        if res is None:break
        time.sleep(random.randint(1,3))
        print(f'{name}吃了{res}')

if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=producer,args=(q,'rocky','包子'))
    p2 = Process(target=producer,args=(q,'mac','韭菜'))
    p3 = Process(target=producer,args=(q,'nick','蒜泥'))
    c1 = Process(target=consumer,args=(q,'成哥'))
    c2 = Process(target=consumer,args=(q,'浩南哥'))
    p1.start()
    p2.start()
    p3.start()
    c1.start()
    c2.start()
    p1.join()# 写了三个生产者的join,保证了生产者生产完毕,也就是生产者进程全结束了。
    p2.join()#为什么这个要写在c1和c2 的start下面,因为这样才有边造边吃的效果
    p3.join() 
    q.put(None)# 几个消费者put几次,一旦消费者接收到None,就会挂掉。
    q.put(None)

既然讲到了包子,顺便也讲一下生产者和消费者!

生产者和消费者

很直白,厨师就是生产者,凉轻松就是消费者。

生产者: 生产数据的任务

消费者: 处理数据的任务

既然这样,我们就可以把上面的所有东西串起来了,用一个栗子来看

不行不行,还要讲一下一个东西。

JoinableQueue

这玩意儿也是在multiprocessing里的。

from multiprocessing import Process,Queue,JoinableQueue


q = JoinableQueue()

q.put('zhao') # 放队列里一个任务
q.put('qian')

print(q.get())
q.task_done() # 完成了一次任务
print(q.get())
q.task_done() # 完成了一次任务
q.join() #计数器不为0的时候 阻塞等待计数器为0后通过

# 想象成一个计数器 :put +1   task_done -1

每当往JoinableQueue队列里放一个东西,他的任务数就+1,每当拿走一个,我们就手动调用一下q的task_done方法,任务数就会-1,队列也是由join方法的,当任务数为0的时候,join才会不阻塞。

现在可以上总的栗子了!

综合实例

from multiprocessing import Process,Queue,JoinableQueue
import time,random

def producer(q,name,food):
    '''生产者'''
    for i in range(3):
        print(f'{name}生产了{food}{i}')
        time.sleep(random.randint(1, 3))
        res = f'{food}{i}'
        q.put(res)
    # q.put(None)

def consumer(q,name):
    '''消费者'''
    while True:
        res = q.get()
        # if res is None:break
        time.sleep(random.randint(1,3))
        print(f'{name}吃了{res}')
        q.task_done() #

if __name__ == '__main__':
    q = JoinableQueue()
    p1 = Process(target=producer,args=(q,'rocky','包子'))
    p2 = Process(target=producer,args=(q,'mac','韭菜'))
    p3 = Process(target=producer,args=(q,'nick','蒜泥'))
    c1 = Process(target=consumer,args=(q,'成哥'))
    c2 = Process(target=consumer,args=(q,'浩南哥'))
    p1.start()
    p2.start()
    p3.start()
    c1.daemon = True
    c2.daemon = True
    c1.start()
    c2.start()
    p1.join()
    p2.join()
    p3.join() # 生产者生产完毕
    # q.put(None)# 几个消费者put几次
    # q.put(None)
    q.join() # 分析
    # 生产者生产完毕--这是主进程最后一行代码结束--q.join()消费者已经取干净了,没有存在的意义了.
    #这是主进程最后一行代码结束,消费者已经取干净了,没有存在的意义了.守护进程的概念.

rocky生产了包子0
mac生产了韭菜0
nick生产了蒜泥0
nick生产了蒜泥1
mac生产了韭菜1
rocky生产了包子1
nick生产了蒜泥2
浩南哥吃了韭菜0
mac生产了韭菜2
成哥吃了蒜泥0
rocky生产了包子2
成哥吃了蒜泥1
浩南哥吃了包子0
浩南哥吃了蒜泥2
浩南哥吃了包子1
成哥吃了韭菜1
浩南哥吃了韭菜2
成哥吃了包子2

至于为什么这么乱,因为操作系统的调度谁也不知道他是怎样的,你不知道他先执行哪个进程等等等等等。最后程序结束。

原文地址:https://www.cnblogs.com/whnbky/p/11528075.html