「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」。
正式的Python专栏第69篇,同学站住,别错过这个从0开始的文章!
队列的设计实现了一个缓冲空间,让系统的吞吐量一下子增大。因为它创造了一个缓冲地带,不管三七二十一,先把大量的,甚至超量的消息(元素之于队列)接纳到了缓冲的地带。
然后程序对外说接受了请求,实际上背后有多个线程(或者调度多个服务器上的进程)把这个缓冲地带的请求陆续处理。
这种模式带来的好处就是‘削峰’的效果,使得系统可以承载超量请求。这也是高并发编程的一个套路:生产者消费者模式。
生产者消费者模型
生产者消费者模式是通过一个中间容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯。
业务表现
生产者生产完数据之后,不用等待消费者处理,直接扔给阻塞队列。
消费者不找生产者要数据,而是直接从阻塞队列里取。
阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
queue很适合作为这个模式的缓冲池(成品池)。
学委制作了下图:
上面就是本文程序展示的,两组线程(左边一组在生产车间生产元素放到成品池queue,然后右边一组线程从队列取出成品元素,在消费车间进行消费活动。 最后成品池就空了。
最左边的生产者跟最右边的消费,彼此之间并无之间通信(没有直接进行线程之间的数据交换),而是通过成品池来做。
举例:类秒杀场景
秒杀场景: 这种活动经常出现在电商平台或者产品抢注类活动中。
更具体的像房地产分批次发配号,释放给买家在同一个时间发起抢购。
抽象一下这类行为:
当且只有产品生产全部完成后,产品对外开放消费(才让消费者在消费车间开始消费)
如下图:
这里有三个阶段:
- 生产阶段
生产车间多个线程不断的生产成品,放入成品池
- 产品发布
直到所有生产活动完成,成品池即将开放给消费车间使用。
- 开始消费
消费车间等候的线程陆续的从 成品池 中取出成品,开始消费活动。
下面学委编写了程序实现了
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2022/2/19 12:40 上午
# @Author : LeiXueWei
# @CSDN/Juejin/Wechat: 雷学委
# @XueWeiTag: CodingDemo
# @File : qdemo_producer_consumer3.py
# @Project : hello
import datetime
import threading
import time
import queue
q = queue.Queue()
threads = []
def operate_q():
time.sleep(1)
tname = threading.current_thread().name
print("%s - tname %s - before put" % (datetime.datetime.now(), tname))
q.put("产品-by %s" % tname)
print("%s - tname %s - before task done" % (datetime.datetime.now(), tname))
time.sleep(3) # 生产产品后的后续工作,比如宣传物料准备等等
q.task_done()
print("%s - tname %s - after task done, q %s" % (datetime.datetime.now(), tname, q.unfinished_tasks))
def get_q():
time.sleep(1.0001)
tname = threading.current_thread().name
print("%s - tname %s - before join - q unfinished_tasks %s" % (
datetime.datetime.now(), tname, q.unfinished_tasks))
q.join()
# print(" %s after join" % tname)
ele = q.get()
print("%s - tname %s - get q: %s" % (datetime.datetime.now(), tname, ele))
for i in range(5):
t = threading.Thread(target=operate_q, name='生产者-' + str(i + 1))
threads.append(t)
t2 = threading.Thread(target=get_q, name='消费者-' + str(i + 1))
threads.append(t2)
# t3 = threading.Thread(target=get_q, name='消费者-' + str(i+5 + 1))
# threads.append(t3)
def monitor():
i = 0
while i < 50:
i += 1
time.sleep(0.1)
tname = threading.current_thread().name
print("monitor-q: %s - q unfinished_tasks %s - queue %s " % (tname, q.unfinished_tasks, q.queue))
threading.Thread(target=monitor, name="学委-q-monitor").start()
for t in threads:
t.start()
下面是运行效果:
这个运行结果很公平,生产5个产品,刚好5个消费者平分了,最后队列为空,产品全部卖完。
如果取消注释t3线程组,那么将会有5个消费者,无法抢到产品。
当然这个秒杀的场景(非严格秒杀),以上代码也保重了消费者是同时等待的,在task_done执行之前所有消费者都已尽准备好了,他们是同时等待unfinished_tasks清0的状态。
一旦unfinished_tasks=0, 那就看这些消费者线程谁的手速快了。
总结
队列类技术组件把不同业务的线程(也可以进程,这个以后再介绍消息队列)隔离开,避免了直接跨线程数据交互,实现了技术解耦。
生产者消费者模型也是高并发场景下的一个技术解决方法,学委觉得很有必要专门在队列解析中补充阐述。
喜欢Python的朋友,请关注学委的 Python基础专栏 or Python入门到精通大专栏
持续学习持续开发,我是雷学委!
编程很有趣,关键是把技术搞透彻讲明白。
欢迎关注微信,点赞支持收藏!