文章目录
环境
- 平台: windows7
- Python版本: 3.7
- 相关模块: queue、 collections、heapq
- 关键词: 队列、 优先级、双向、令牌桶
文章介绍
本文从以下几个部分循序渐进的记录queue在Python中的场景, 最终我们以一个自定的优先级队列
涵盖说明文章的所有知识点
- 1.queue的使用方式和使用场景及queue派生的其余队列的使用
- 2.deque双向队列的使用
- 3.heapq堆的使用
- 4.令牌桶的实现
- 5.自定义(派生)queue
一、queue (线程安全)
3种不同的队列
根据源码来看, queue提供了重写队列的方法,重写部分如下
1. Queue, 先进先出队列(FIFO)
# Override these methods to implement other queue organizations
# (e.g. stack or priority queue).
# These will only be called with appropriate locks held
# Initialize the queue representation
def _init(self, maxsize):
self.queue = deque() # 双向队列 被 self.__init__调用
def _qsize(self):
return len(self.queue) # self.qsize 调用
# Put a new item in the queue
def _put(self, item): # self.put 调用
self.queue.append(item)
# Get an item from the queue
def _get(self): # self.get调用
return self.queue.popleft()
2.LifoQueue(派生自Queue), 先进后出队列(LIFO)
'''Variant of Queue that retrieves most recently added entries first.'''
def _init(self, maxsize):
self.queue = []
def _qsize(self):
return len(self.queue)
def _put(self, item):
self.queue.append(item)
def _get(self):
return self.queue.pop()
3.PriorityQueue(派生自Queue), 优先级队列
'''Variant of Queue that retrieves open entries in priority order (lowest first).
Entries are typically tuples of the form: (priority number, data).
'''
def _init(self, maxsize):
self.queue = []
def _qsize(self):
return len(self.queue)
def _put(self, item):
heappush(self.queue, item)
def _get(self):
return heappop(self.queue)
常用公共方法
-
Queue.qsize()
获取队列的长度(大小), 参考值(非准确值) -
Queue.empty()
队列为空返回True
, 参考值(非准确值) -
Queue.full()
队列满返回True
, 参考值(非准确值) -
Queue.put(item, block=True, timeout=None)
将item
放入队列,如果队列已满且block为True
则等待timeout
秒后抛出Full
异常,如果timeout
为None
则一直阻塞直到队列空闲;如果队列已满且block
为False
则直接抛出Full
异常 -
Queue.put_nowait(item)
Queue.put(item, block=False) -
Queue.get(block=True, timeout=None)
从队列中移除并返回一个item
, 如果队列为空且block为True
则等待timeout
秒后抛出Empty
异常,如果timeout
为None
则一直阻塞直到队列有值;如果队列为空且block
为False
则直接抛出Empty
异常 -
Queue.get_nowait()
Queue.get(block=False)
- Queue.task_done()
消费线程调用,告知队列该任务的处理已经完成,和join
配合,代表每一个放入队列的任务都已经处理完毕,join取消阻塞 - Queue.join()
阻塞直到队列中的所有条目都获取并处理完毕, 查看源码很简单: 即put
一个元素,未完成任务的计数就会增加, 每调用task_done
就会 -1,直到计数归0,join
取消阻塞 - 简单阐明 task_done+join
from queue import Queue q = Queue() q.put(1) q.task_done() # 如果不调用这行 queue会在 join那里直接阻塞,因为queue的技术没有归零, 这个在多线程中多消费对生产者的工作成果汇报还是很有用的 q.join()
二、deque - 双端队列
A list-like sequence optimized for data accesses near its endpoints
常用方法
- 基础方法
方法 作用 doc append 右端追加 Add an element to the right side of the deque appendleft 左端追加 Add an element to the left side of the deque insert 类似于list.insert D.insert(index, object) – insert object before index extend 右端扩展(类似于 list.extend) Extend the right side of the deque with elements from the iterable extendleft 左端扩展 Extend the left side of the deque with elements from the iterable pop 删除并返回右端元素 Remove and return the rightmost element popleft 删除并返回左端元素 Remove and return the leftmost element remove 类似于list.remove D.remove(value) – remove first occurrence of value. clear 清空deque Remove all elements from the deque copy 返回浅拷贝的deque Return a shallow copy of a deque count 返回deque元素的数量 D.count(value) -> integer – return number of occurrences of value index(item, [start, [stop]]) 返回元素在deque中的在start-stop之间第一次出现的索引(类似于list.index(item)) D.index(value, [start, [stop]]) -> integer – return first index of value. Raises ValueError if the value is not present. reverse 反转 类似于list.reverse D.reverse() – reverse IN PLACE rotate(n) 右旋n步 Rotate the deque n steps to the right (default n=1). If n is negative, rotates left. - 魔法方法
支持 list 的几乎所有方法, 如:list1+list2
,item in list
…
三、 heapq
数据结构-堆, 常用来实现优先级队列
基础方法
-
最小堆
方法 作用 heappush(heap, item) item入堆,保持堆不变 heappop(heap) 弹出最小的元素,保持堆不变 heapreplace(heap, item) 弹出+入堆 heappop+heappush heappushpop(heap, item) 入堆+弹出 先入堆再弹出 heapify(list) 列表转换为堆 O(n) -
最大堆
方法 作用 _heappop_max(heap) 弹出最大的元素,保持堆不变 _heapreplace_max(heap, item) 弹出最大值, 推入item _heapify_max(list) 将list转换为最大堆 -
其他常用
方法 作用 nlargest(n, heap, key=None, cmp=None) 返回heap中最大的n个数, 参数参考list.sort nsmallest(n, heap, key=None, cmp=None) 返回heap中最小的n个数, 参数参考list.sort
四、TokenBucket(令牌桶) - 限流
- 实现
import threading, time
class TokenBucket:
def __init__(self, rate=1, overflow=10):
"""
rate: 令牌生产流控的速率, 1表示1s产生一个令牌
overflow: 桶的容量, 产生的令牌总数不能超过这个值,否则溢出
"""
self.rate = float(rate)
self._overflow = overflow
self._bucket = self._overflow # 初始化令牌桶数量为临界值
self._mutex = threading.Lock() # 线程安全锁(互斥锁)
self.last_update = int(time.time()) # 时间戳
@property
def is_overflow(self): # 判断是否溢出
return self._bucket >= self._overflow
def is_legal(self, now, num):
if self._bucket >= num:
self.last_update = now
return 1
else:
return 0
def get(self, num=1):
now = int(time.time())
if self.is_overflow:
self.last_update = now
else:
increment = (int(now) - int(self.last_update)) * self.rate
if increment > 1: # 令牌的递增以最小颗粒度1为原则
self._bucket += increment
self._bucket = min(self._bucket, self._overflow)
return self.is_legal(now, num)
def set(self, value):
self._bucket = value
def desc(self, num=1):
self._bucket -= num
五、自定义优先级队列
本节以三步来自定义一个优先级队列并给出调用实例
- 1.封装任务包作为队列的
item
一个拥有基础功能的任务包就定义好了, 可以比较大小(heapq模块会调用)、迭代、索引等class Task: __slots__ = ('task', 'priority', 'exetime') # 优化内存, 限定Task实例的属性范围 __getitem__ = lambda *x: getattr(*x) # 定义 Task[n] 方式取值的方法 __setitem__ = lambda *x: setattr(*x) # 定义 Task[n] = item 方式设值的方法 __iter__ = lambda self: iter(self.__slots__) # 定义了可迭代对象 __len__ = lambda self: len(self.__slots__) # len()方法 def __init__(self, item, priority=0, exetime=0): self.priority = priority self.task = item self.exetime = exetime def __cmp__(self, other): cmp = lambda x, y: (x > y) - (x < y) if self.exetime == 0 and other.exetime == 0: return -cmp(self.priority, other.priority) # 优先级值取负值 else: return cmp(self.exetime, other.exetime) # 只要exetime带值,就比较exetime的值,时间值的优先级高于`priority` def __lt__(self, other): return self.__cmp__(other) < 0
- 2.封装优先级队列
import queue as Queue import heapq class PriorityQueue(Queue.Queue): def _init(self, maxsize): self.queue = [] self.record = {} # 队列元素记录 def _qsize(self): return len(self.record) # Put a new item in the queue def _put(self, item): if item.task in self.record: task = self.record[item.task] changed = False if item.priority > task.priority: task.priority = item.priority # 重设优先级 为大方 changed = True if item.exetime < task.exetime: # 重设时间 为小方 task.exetime = item.exetime changed = True if changed: # 有变化 self._resort() else: heapq.heappush(self.queue, item) self.record[item.task] = item def _get(self): while self.queue: item = heapq.heappop(self.queue) if item.task is None: continue self.record.pop(item.task, None) return item return None def _resort(self): heapq.heapify(self.queue) def __contains__(self, task): return task in self.record def __getitem__(self, task): return self.record[task] def __setitem__(self, task, item): assert item.task == task self.put(item) def __delitem__(self, task): self.record.pop(task).task = None
- 优先级队列使用+令牌桶限速
class TaskQueue: """ 使用优先级队列的任务队列封装 """ def __init__(self): self.priority_queue = PriorityQueue() self.bucket = TokenBucket(rate=1, overflow=10) self.mutex = threading.RLock() def get(self, block=True): # 令牌桶限速的get if self.bucket.get(): with self.mutex: try: item = self.priority_queue.get(block=block) self.bucket.desc() # 令牌桶 -1 return item except Queue.Empty as err: pass def put(self, **kwargs): task = Task(item=kwargs["item"], priority=kwargs.get("priority", 0), exetime=kwargs.get("exetime", 0)) self.priority_queue.put(task) @property def rate(self): return self.bucket.rate @rate.setter def rate(self, rate): self.bucket.rate = rate @property def overflow(self): return self.bucket._overflow @overflow.setter def overflow(self, overflow): self.bucket._overflow = overflow def __len__(self): return self.priority_queue.qsize() def delete(self, task): with self.mutex: if task in self.priority_queue: del self.priority_queue[task]