优先级队列 Python

本文介绍了Python中的queue模块,包括Queue、LifoQueue和PriorityQueue三种不同类型的队列,详细阐述了它们的使用方法和特性。此外,还讲解了deque双端队列、heapq堆以及TokenBucket令牌桶限流的实现。最后,通过自定义优先级队列展示了如何结合使用这些数据结构来实现复杂的功能。
摘要由CSDN通过智能技术生成

环境

  • 平台: 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 异常,如果timeoutNone 则一直阻塞直到队列空闲;如果队列已满且blockFalse则直接抛出Full异常

  • Queue.put_nowait(item)
    Queue.put(item, block=False)

  • Queue.get(block=True, timeout=None)
    从队列中移除并返回一个 item, 如果队列为空且block为True则等待timeout秒后抛出 Empty 异常,如果timeoutNone 则一直阻塞直到队列有值;如果队列为空且blockFalse则直接抛出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.insertD.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.removeD.remove(value) – remove first occurrence of value.
    clear清空dequeRemove all elements from the deque
    copy返回浅拷贝的dequeReturn 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.reverseD.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
    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
    		
    
    一个拥有基础功能的任务包就定义好了, 可以比较大小(heapq模块会调用)、迭代、索引等
  • 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
    
  1. 优先级队列使用+令牌桶限速
    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]
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值