python之进程互斥锁、队列、IPC机制(进程间通信)、生产者消费者模型

1、进程互斥锁

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

def search():
    # 查票的函数
    with open('ticket.txt', mode='rt', encoding='utf-8') as fp:
        dic = json.load(fp)
        print('当前剩余票数是{}'.format(dic.get('ticket')))

def buy(n):
    with open('ticket.txt', mode='rt', encoding='utf-8') as fp:
        dic = json.load(fp)
        print('当前剩余票数是{}'.format(dic.get('ticket')))

    time.sleep(random.randint(1, 3))  # 模拟网络延迟
    if dic.get('ticket') > 0:
        # 能够买票
        dic['ticket'] -= 1
        # 保存到文件中去
        with open('ticket.txt', mode='wt', encoding='utf-8') as fp:
            json.dump(dic, fp)
        print('恭喜用户{}买票成功'.format(n))
    else:
        # 买票失败
        print('用户{}买票失败,余票不足'.format(n))

# 定义一个方法,先查票,再买票
def task(mutex, i):
    # 第一种方式
    search()
    # 买票过程要加锁
    mutex.acquire()
    buy(i)  # 10个进程变成了串行执行
    # 买后释放锁
    mutex.release()

    # 第二种方式
    search()
    # 买票过程要加锁
    with mutex:
        buy(i)  # 10个进程变成了串行执行

if __name__ == '__main__':
    # 创建一把锁
    mutex = Lock()
    # 模拟十个人买票(开10个进程)
    for i in range(1, 11):
        t = Process(target=task, args=(mutex, i))
        t.start()

ticket.txt文件内容:
{"ticket": 0}

2、队列

# 实例化得到对象
q = Queue(3)  # 默认很大,可以放很多,写了3,只能放3个
# 往队列中放值
q.put('allen')
q.put(18, timeout=1)  # 能放进去就放进去 等1s还放不进去,就报错
q.put_nowait('male')  # 队列满了,放不进去就不放了,立刻报错
q.put('school')  # 多传程序阻塞在原地

# 从队列中取值
print(q.get())  # 从队列头部取出一个值
print(q.get(timeout=1))  # 等1s还没有值,就报错
print(q.get_nowait())  # 从队列头部取值,没有就抛错
print(q.get())  # 多传程序阻塞在原地
print(q.full())  # 看一下队列是不是满的
print(q.empty())  # 看一下队列是不是空的

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

Inter-Process Communication: 进程间通信

from multiprocessing import Process, Queue
import os

def task1(q):
    print('我是task1进程,我的进程id是: {}'.format(os.getpid()))
    q.put('allen')
    q.put(18)

def task2(q):
    print('我是task2进程,我的进程id是: {}'.format(os.getpid()))
    print('task2在队列中取到的数据是: {}'.format(q.get()))

if __name__ == '__main__':
    q = Queue(2)
    t1 = Process(target=task1, args=(q,))
    t1.start()
    t2 = Process(target=task2, args=(q,))
    t2.start()
    print('主进程在队列中取到的数据是: {}'.format(q.get()))

4、生产者和消费者模型

4.1、定义
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度
4.2、为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式
4.3、什么是生产者消费者模式
1、生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题
2、生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取
3、阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力

在这里插入图片描述

4.4、基于队列实现生产者消费者模型
4.4.1、multiprocessing - Queue 实现-简易版本
from multiprocessing import Process, Queue  # 多进程组件,队列
import time, random


# 生产者方法
def producer(name, food, q):
    for i in range(4):
        time.sleep(random.randint(1, 3))  # 模拟获取数据时间
        f = '{}生产的{}{}'.format(name, food, i)
        print(f)
        q.put(f)  # 添加进队列
        time.sleep(random.randint(1, 3))  # 模拟消耗数据时间


# 消费者方法
def consumer(q, name):
    while True:
        food = q.get()  # 如果获取不到,会一直阻塞进程不会结束子进程
        # 当队列中的数据是None的时候结束while循环
        if food is None: break
        f = '{}消费了{}'.format(name, food)
        print(f)
        time.sleep(random.randint(1, 3))  # 模拟消耗数据时间


if __name__ == '__main__':
    q = Queue()  # 创建队列

    # 模拟生产者 生产数据
    p = Process(target=producer, args=('张三', '包子', q))  # 创建进程
    p.start()  # 启动进程
    p1 = Process(target=producer, args=('李四', '面条', q))
    p1.start()

    # 模拟消费者消费数据
    c = Process(target=consumer, args=(q, '王五'))
    c.start()
    c1 = Process(target=consumer, args=(q, '张六'))
    c1.start()

    p.join()  # 阻塞主进程 直到p和p1 子进程结束后才执行q.put() 方法
    p1.join()  # 阻塞主进程 直到p和p1 子进程结束后才执行q.put() 方法

    # 为了确保生产者生产完所有数据后,
    # 最后一个是None,方便结束子进程中的while循环,
    # 否则会一直等待队列中加入新数据。
    q.put(None)
    q.put(None)

总结:
   使用Queue组件实现的缺点就是,实现了多少个消费者consumer进程,就需要在最后往队列中添加多少个None标识,方便生产完毕结束消费者consumer进程。否则,p.get() 不到任务会阻塞子进程,因为while循环,直到队列q中有新的任务加进来,才会再次执行。而我们的生产者只能生产这么多东西,所以相当于程序卡死
4.4.2、multiprocessing - JoinableQueue 实现-终极版本
from multiprocessing import JoinableQueue, Process
import time, random


# 生产者方法
def producer(name, food, q):
    for i in range(4):
        time.sleep(random.randint(1, 3))
        f = '{}生产的{}{}'.format(name, food, i)
        q.put(f)
        print(f)
    q.join()  # 一直阻塞,等待消耗完所有的数据后才释放


# 消费者方法
def consumer(name, q):
    while True:
        food = q.get()
        print('{}消费了{}'.format(name, food))
        time.sleep(random.randint(1, 3))
        q.task_done()  # 每次消耗减1


if __name__ == '__main__':
    q = JoinableQueue()  # 创建队列
    # 模拟生产者队列
    p1 = Process(target=producer, args=('张三', '包子', q))
    p1.start()
    p2 = Process(target=producer, args=('李四', '面条', q))
    p2.start()

    # 模拟消费者队列
    c1 = Process(target=consumer, args=('王五', q))
    c1.daemon = True  # 守护进程:主进程结束,子进程也会结束
    c1.start()
    c2 = Process(target=consumer, args=('张六', q))
    c2.daemon = True
    c2.start()

    p1.join()  # 阻塞主进程,等到p1子进程结束才往下执行
    p2.join()

    # q.task_done() 每次消耗队列中的 任务数减1
    # q.join() 一直阻塞,等待队列中的任务数消耗完才释放
    # 因为有 q.join 所有一直会等待 c1,c2 消耗完毕。才会执行 p.join 后面的代码
    # 因为 c1 c2 是守护进程,所以到这一步主进程代码执行完毕,主进程会释放死掉,
    # 所以 c1 c2 也会跟随 主进程释放死掉

总结:
   使用JoinableQueue组件,是因为JoinableQueue中有两个方法: task_done()和join() 。首先说join()和Process中的join()的效果类似,都是阻塞当前进程,防止当前进程结束。但是JoinableQueue的join()是和task_down()配合使用的。
  Process中的join()是等到子进程中的代码执行完毕,就会执行主进程join()下面的代码。而JoinableQueue中的join()是等到队列中的任务数量为0的时候才会执行q.join()下面的代码,否则会一直阻塞。
  task_down()方法是每获取一次队列中的任务,就需要执行一次。直到队列中的任务数为0的时候,就会执行JoinableQueue的join()后面的方法了。所以生产者生产完所有的数据后,会一直阻塞着。不让p1和p2进程结束。等到消费者get()一次数据,就会执行一次task_down()方法,从而队列中的任务数量减1,当数量为0后,执行JoinableQueue的join()后面代码,从而p1和p2进程结束。
  因为p1和p2添加了join()方法,所以当子进程中的consumer方法执行完后,才会往下执行。从而主进程结束。因为这里把消费者进程c1和c2 设置成了守护进程,主进程结束的同时,c1和c2 进程也会随之结束,进程都结束了。所以消费者consumer方法也会结束
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值