文章目录
1. 进程之间的通信
1.1基于文件方式
-
需求
我们要完成一个抢票系统,12306,现在10个人进行查票,这个是并行或者并发的效果,但是买票的过程一定是串行的。我们用代码简单实现。
进程在内存中原则上来说数据时不能共享的,但是进程可以共享磁盘。所以我们的余额数据应该存入文件中。
from multiprocessing import Process,Lock import json import os import time import random def check(): time.sleep(random.randint(1, 2)) with open('ticket.json', mode='r', encoding='utf-8') as f1: dic = json.load(f1) print(f'{os.getpid()}查询了票数,剩余{dic["num"]}票') f1.close() def buy(): with open('ticket.json', mode='r', encoding='utf-8') as f1: dic = json.load(f1) f1.close() if dic['num'] > 0: dic['num'] -= 1 time.sleep(random.randint(1, 2)) with open('ticket.json', mode='w', encoding='utf-8') as f1: json.dump(dic, f1) f1.close() print(f'{os.getpid()}进程购买成功!') else: print('票已售完') def task(lock): check() lock.acquire() buy() lock.release() if __name__ == '__main__': lock = Lock() for i in range(10): p = Process(target=task,args=(lock,)) p.start()
1.2基于队列的方式
1.2.1队列的学习
队列,就是Queue,他是一个容器型的数据类型,这个容器型的数据类型比较特殊,他的原则是先进先出原则。就是因为先进先出的原则,他是不需要我们手动加锁的。他是进程之间最常用的通信方式。
from multiprocessing import Queue
# q = Queue(3)
# 先进先出原则
# q.put('barry')
# q.put('玮哥')
# q.put(666)
#
# print(q.get())
# print(q.get())
# print(q.get())
q = Queue(3)
q.put('barry')
q.put('玮哥')
q.put(666)
# q.put('冲哥') # 当你插入的数据超过最大值时,队列默认阻塞直到其他进程取值。
# q.put('冲哥',block=False) # 当你插入的数据超过最大值时,队列默认阻塞直到其他进程取值。
print(q.get())
print(q.get())
print(q.get())
# print(q.get(block=False))
print(q.get(timeout=3)) # 阻塞3秒,3秒过后再无值插入,则报错。
注:
队列其他参数:
- 队列里的数量不宜过大。进程之间的通信,就是通知、链接等小数据。(不宜放入视频,图片等)
- maxsize = 10000000虽然可以设置无限大,但是队列存在内存中,不宜过大
- block = True: 超过上限默认阻塞, block = False , 超过上限直接报错。
- timeout: 设置阻塞时间, 超出阻塞时间还处于阻塞状态就会报错。
1.3,基于管道的方式(不推荐使用)
1.3.1创建管道的类
Pipe([duplex]):在进程之间创建一条管道, 并返回元组(conn1, conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道。
1.3.2参数介绍
dumplex:默认管道是全双工的,如果将dumplex设置成False,conn1只能用于接收,conn2只能用于发送。
1.3.3主要方法
conn1.close():关闭连接。如果conn1被垃圾回收,将自动调用此方法
conn1.fileno():返回连接使用的整数文件描述符
conn1.poll([timeout]):如果链接上的数据可用,返回True。timeout指定等待的最长时限。如果省略此参数,方法将立即返回结果。如果将timeout设置成None,操作将无限的等待数据到达。
conn1.recv_bytes([maxlength]):接收c.send_bytes()方法发送的一条完整的字节消息。maxlength指定要接收的最大字节数。如果进入的消息超过了这个最大值,将引发IOError异常,并且在连接上无法进行进一步读取。如果连接的另外一端已经关闭,再也不存在任何数据,将引发EOFError异常。
**conn.send_bytes(buffer[, offset [, size]])😗*通过链接发送字节数据缓冲区,buffer是支持缓冲区接口的任意对象,offset是缓冲区中的字节偏移量,而size是要发送字节数。结果数据以单条消息的形式发出,然后调用c.recv_bytes()函数进行接收。
**conn1.recv_bytes_into(buffer[, offset])😗*接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。
-
代码展示
from multiprocessing import Process,Pipe import time,os def consumer(p,name): left,right=p left.close() while True: try: baozi=right.recv() print('%s 收到包子:%s' %(name,baozi)) except EOFError: right.close() break def producer(seq,p): left,right=p right.close() for i in seq: left.send(i) # time.sleep(1) else: left.close() if __name__ == '__main__': left,right=Pipe() c1=Process(target=consumer,args=((left,right),'c1')) c1.start() seq=(i for i in range(10)) producer(seq,(left,right)) right.close() left.close() c1.join() print('主进程')
但是,管道是有问题的,管道会造成数据的不安全,官方给予的解释是管道有可能会造成数据损坏。
2.生产者消费者模型
2.1引子
无论你接触过的模型,设计模型,理论知识,都是用特别高大上的话术给你描述的云里雾里的。核心就是告诉你一种代码的写作方法,套路。
**生产者:**生产数据的一方(进程)就叫做生产者。
**消费者:**接收数据并进行下一步操作就是消费者。
生活中的例子:厨师生产东西,顾客吃东西,这就是生产者,消费者。
我生产出包子,然后怎么给他吃?应该是有一个盆,我把包子放在盆里,然后他从盆里取包子吃,这就是平时咱们见到的例子。假设,没有这个盆了:我生产一个,他吃一个,他吃的过程中,我不会继续生产包子,等他吃完了,我在生产包子,他吃的时候我是不能生产的,我们两个紧紧耦合在一起,这样是不科学的,对吧。厨师就是生产包子,不管你吃不吃完,都是生产包子,然后给盆,消费者跟盆打交道,他不会跟出厨子打交道的。所以:厨师这一类人就是生产包子,然后跟盆打交道,而消费者也是一类人,他们也是直接跟盆打交道。 一个盆解耦了,非常关键的一点,平衡了两者之间的生产力和消费力,存在,生产者就会一直生产,消费者就会一直消费,这个盆作为解耦概念。平时去快餐店吃饭就是这个道理。
消费者起一个进程,生产者起一个进程,盆就是队列。
生产者消费者模型三要素:生产者,消费者,队列
from multiprocessing import Process,Queue
import time
import random
def producer(q):
for i in range(1,6):
time.sleep(random.randint(1, 3))
q.put(f'{i}号包子')
print(f'生产出了{i}号包子')
def consumer(q):
while 1:
res = q.get()
time.sleep(random.randint(1, 2))
print(f'消费者吃了{res}')
if __name__ == '__main__':
q = Queue(5)
p1 = Process(target=producer,args=(q,))
p2 = Process(target=consumer,args=(q,))
p1.start()
p2.start()