并发编程之进程间通信、生产者消费者模型
目录
并发编程之进程间通信、生产者消费者模型
一 进程调度
(1) 先来先服务
(2) 短作业优先
(3) 时间片轮转
(4) 多级反馈队列
多级反馈队列调度算法即能使高优先级的作业得到响应又能使短作业(进程)迅速完成。
二 僵尸进程与孤儿进程
(1) 僵尸进程
进程结束了,资源还没来得及回收完成。
(2) 孤儿进程
主进程结束了,子进程还没结束,它就会被专门的进程接管。
三 进程对象及其他方法
# 1. windows: tasklist |findstr 进程id号 # 查看进程ID号码
# 2. mac,linux: ps aux | grep 进程id号 # 查看进程ID号码
# 3. t = Process(target=task,) 或者在进程内部:current_process() # 进程对象
# 4. t.pid 或者 current_process().pid # 获取进程pid
# 5. os.getpid() # 获取进程pid
# 6. os.getppid() # 获取父进程id号,子进程装获取父进程id,等于主进程的id号
# 7. t.is_alive()或者current_process().is_alive() # 查看进程是否存活
# 8. t.terminate() # 关闭进程,在主进程关闭
四 守护进程
主进程创建守护进程:主进程一旦结束,守护进程(子进程)也随之结束;守护进程内无法开启子进程,否则会抛出异常。
注意:进程之间是相互独立的,主进程代码结束,守护进程随之终止。
# 守护进程
from multiprocessing import Process, current_process
import time
import os
def task():
print(os.getpid())
print('子进程')
time.sleep(200)
print('子进程结束')
if __name__ == '__main__':
t = Process(target=task,)
# 守护进程:主进程一旦结束,子进程也结束
# t.daemon = True
t.start()
time.sleep(1)
print('主进程结束!')
五 互斥锁(进程同步锁)
1 为什么要有互斥锁
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或是同一个打印终端,是没有问题的,但是共享带来了竞争,竞争带来的结果是错乱,需要加锁控制。
2 如何使用互斥锁
# (1) 例一:多个进程共享同一打印终端
# 并发运行,效率高,但竞争同一打印终端,带来了打印错乱
from multiprocessing import Process
import os, time
def work():
print('%s is running' % os.getpid())
time.sleep(2)
print('%s is done' % os.getpid())
if __name__ == '__main__':
for i in range(3):
p = Process(target=work, )
p.start()
# 由并发变成了串行,牺牲了运行效率,但避免了竞争
from multiprocessing import Process, Lock
import os, time
def work(mutex):
# 先加锁
mutex.acquire()
print('%s is running' % os.getpid())
time.sleep(2)
print('%s is done' % os.getpid())
# 然后释放锁,让后续进程可获取服务
mutex.release()
if __name__ == '__main__':
# 主进程创建锁
mutex = Lock()
for i in range(3):
p = Process(target=work, args=(mutex,))
p.start()
# (2) 例二:多个进程共享同一文件,文件当数据库,模拟抢票:
# 同时只有一个人能拿到,必须释放,其他人才能拿到,
# 以火车票购买为例
# 文件ticket的内容为:{"ticket_count":1}
from multiprocessing import Process, Lock
import json
import time
import random
def search():
# 查票函数
# 打开文件,读出ticket_count
with open('ticket', mode='rt', encoding='utf-8') as f:
dic = json.load(f)
print('余票数量', dic.get('ticket_count'))
def buy():
with open('ticket', 'r', encoding='utf-8') as f:
dic = json.load(f)
time.sleep(random.randint(1, 3)) # 模拟一下网络延迟
if dic.get('ticket_count') > 0: # 可以买票
dic['ticket_count'] -= 1
with open('ticket', 'w', encoding='utf-8') as f: # 保存到文件中
json.dump(dic, f)
print('买票成功!')
else:
print('买票失败!')
# 写一个函数,先查票,后买票
def task(mutex):
search()
# 买票过程要加锁
# 方式一:
# 买之前加锁
# mutex.acquire()
# buy()
# 买后释放锁
# mutex.release()
# 方式二:
with mutex:
buy()
if __name__ == '__main__':
mutex = Lock() # 在主进程创建锁
for i in range(10): # 模拟十人买票(开10个进程)
t = Process(target=task, args=(mutex,))
t.start()
3 总结:
(1) 加锁可以保证多个进程修改同一文件的数据时, 同一时间只有一个文件可以进行修改,即串行的修改,虽然牺牲了效率(文件基于硬盘,需要自己处理加锁的问题),但是保证数据的安全。
(2) 需要一种方案可以解决效率低的问题,可以让多进程共用内存数据,以及帮助我们处理好锁的问题。multiprocess模块为我们提供了基于IPC的通信机制:队列(基于管道+锁实现的)和管道。
六 IPC机制(进程间通信)之队列介绍
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
1 什么是IPC机制
Inter-Process Communication,进程间通信,可以在不同进程之间传播或交换信息。
2 如何使用队列
2.1 创建队列的类(底层就是以管道和锁定的方式实现):
Queue([maxsize]) : 创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
# 使用
q = Queue(5) # 实例化一个队列,最大值五个项目
2.2 参数:
maxsize 是队列中允许的最大项数,省略则为默认值,是一个很大的数字。
2.3 方法介绍
# 主要方法q.put() # (放值)该方法可以插入数据到队列,put方法还有两个可选参数:blocked和timeout。如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为False,但该Queue已满,会立即抛出Queue.FUll异常。q.put_nowait() # (放值)同q.put(False)q.get() # (取值)该方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数blocked和timeout。如果blocked为True(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为False,有两种情况存在,如果Queue有一个值可用,e立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常。q.get_nowait() # (取值)同q.get(False)q.empty() # 调用此方法时q为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了新项目。q.full() # 调用此方法时q已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中项目被取走。q.qsize() # 返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样。
# 其他方法q.cancel_join_thread() # 不会在进程退出时自动连接后台线程。可以防止join_thread()方法阻塞。q.close() # 关闭队列,防止队列中加入更多数据。调用此方法,后台线程将继续写入那些已经入队列但尚未写入的数据。但将在此方法完成时马上关闭。如果q被垃圾收集,将调用此方法。关闭队列不会在队列使用者中产生任何类型的结束信号或异常。例如,如果某个使用者正在被阻塞在get()操作上,隔壁生产者中的队列不会导致get()方法返回错误。q.join_thread() # 连接队列的后台线程。此方法用于在调用q.close()方法之后,等待所有队列想被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread方法可以禁用这种行为。
2.4 使用示例
from multiprocessing import Process, current_process, Queueimport timeimport osdef task1(q): print('我是task1进程,我的id号是%s' % os.getpid()) q.put('lxx is xxx')def task2(q): # res = q.get() # print('我是task2进程,我的id号是%s' % os.getpid(), res) print('我是task2进程,我的id号是%s' % os.getpid())if __name__ == '__main__': q = Queue(5) t1 = Process(target=task1, args=(q,)) t1.start() t2 = Process(target=task2, args=(q,)) t2.start() print(q.get()) # 可以在主进程与子进程之间使用队列,也可以在子进程之间使用队列
七 生产者与消费者模型
1 生产者消费者模型
### 1 生产者消费者模型 在并发编程中是用生产者与消费者模型可以解决绝大多数并发问题。该模型通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。### 2 为何要使用 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者处理能力大于生产者,那么消费者必须等待生产者。为了解决这个问题,于是就引入了生产者消费者模式。### 3 什么是生产者消费者模式 生产者消费者模式是通过一个**容器**来解决生产者和消费者的强耦合问题。 生产者和消费者**彼此之间不直接通讯**,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取. 阻塞队列相当于一个**缓冲区**,平衡了生产者和消费者的处理能力。
2 生产者消费者模型总结
1) 程序中有两类角色 一类负责生产数据(生产者) 一类负责处理数据(消费者) 2) 引入生产者消费者模型是为了解决问题: 平衡生产者与消费者之间的工作能力,从而提高程序整体处理数据的速度 3) 如何实现: 生产者<-->队列<-->消费者 4) 生产者消费者模型实现类似程序的解耦合。
3 基于队列实现生产者消费者模型
1) # 初级生产者消费者模型import timeimport randomfrom multiprocessing import Process, Queuedef producer(name, food, q): for i in range(10): data = '%s 制造了 %s' % (name, food) # 模拟延迟 time.sleep(random.randint(1, 3)) print(data) q.put(food)def consumer(name, q): while True: food = q.get() # 模拟食物吃的延迟 time.sleep(random.randint(1, 3)) print('%s 消费了%s' % (name, food))if __name__ == '__main__': q = Queue() # 创造一个生产者 p = Process(target=producer, args=('lxx', '包子', q)) p.start() # 创造一个消费者 c = Process(target=consumer, args=('egon', q)) c.start()
2) # 解决如果queue中没有数据了,消费者会一直卡住import timeimport randomfrom multiprocessing import Process, Queuedef producer(name, food, q): for i in range(10): data = '%s 制造了 %s' % (name, food) # 模拟延迟 time.sleep(random.randint(1, 3)) print(data) q.put(food) q.put(None) # 制造一个Nonedef consumer(name, q): while True: food = q.get() if food is None: return # 当队列中取出None,进程结束 # 模拟食物吃的延迟 time.sleep(random.randint(1, 3)) print('%s 消费了%s' % (name, food))if __name__ == '__main__': q = Queue() # 创造一个生产者 p = Process(target=producer, args=('lxx', '包子', q)) p.start() # 创造一个消费者 c = Process(target=consumer, args=('egon', q)) c.start()
3)# 制造两个消费者import timeimport randomfrom multiprocessing import Process, Queuedef producer(name, food, q): for i in range(10): data = '%s 制造了 %s' % (name, food) # 模拟延迟 time.sleep(random.randint(1, 3)) print(data) q.put(food)def consumer(name, q): while True: food = q.get() if food is None: return # 当队列中取出None,进程结束 # 模拟食物吃的延迟 time.sleep(random.randint(1, 3)) print('%s 消费了%s' % (name, food))if __name__ == '__main__': q = Queue() # 创造一个生产者 p = Process(target=producer, args=('lxx', '包子', q)) p.start() # 创造两个消费者 c = Process(target=consumer, args=('egon', q)) c.start() c1 = Process(target=consumer, args=('矮根', q)) c1.start() # 生产者生产完毕,放两个None p.join() # 等待p进程执行完成再放 q.put(None) q.put(None)
4)# 多个生产者和多个消费者import timeimport randomfrom multiprocessing import Process, Queuedef producer(name, food, q): for i in range(10): data = '%s 制造了 %s' % (name, food) # 模拟延迟 time.sleep(random.randint(1, 3)) print(data) q.put(food)def consumer(name, q): while True: food = q.get() if food is None: return # 当队列中取出None,进程结束 # 模拟食物吃的延迟 time.sleep(random.randint(1, 3)) print('%s 消费了%s' % (name, food))if __name__ == '__main__': q = Queue() # 创造两个生产者 p = Process(target=producer, args=('lxx', '包子', q)) p.start() p1 = Process(target=producer, args=('天津饭', '饺子皮', q)) p1.start() # 创造两个消费者 c = Process(target=consumer, args=('egon', q)) c.start() c1 = Process(target=consumer, args=('矮根', q)) c1.start() # 生产者生产完毕,放两个None p.join() # 等待p进程执行完成再放 p1.join() # 等待p1(另一个生产者) 进程执行完成再放 q.put(None) q.put(None)
5)# 最终版本,不用放Noneimport timeimport randomfrom multiprocessing import Process, Queue, JoinableQueuedef producer(name, food, q): for i in range(10): data = '%s 制造了 %s' % (name, food) # 模拟延迟 time.sleep(random.randint(1, 3)) print(data) q.put(food)def consumer(name, q): while True: food = q.get() # 模拟食物吃的延迟 time.sleep(random.randint(1, 3)) print('%s 消费了%s' % (name, food)) q.task_done() # 把队列中维护的数字减一if __name__ == '__main__': q = JoinableQueue() # 内部设置了一个数字,放一个数字加一,取一个数字减一:q.task_done() # 创造两个生产者 p = Process(target=producer, args=('lxx', '包子', q)) p.start() p1 = Process(target=producer, args=('天津饭', '饺子皮', q)) p1.start() # 创造两个消费者 c = Process(target=consumer, args=('egon', q)) c.start() c1 = Process(target=consumer, args=('矮根', q)) c1.start() # 等待所有生产者生产结束,主进程再结束 p.join() p1.join() q.join() # 会卡住,一直等待q队列中数据没有了,才继续往下走 print('生产者全部结束,主进程结束')
4 了解JoinableQueue()的用法
q = JoinableQueue() # 实例化一个队列,每放一个值,数字加一q.task_done() # 取值会减一,q.join() # 一直阻塞,当q没有值了,才继续走

浙公网安备 33010602011771号