python 64式: 第50式、queue源码分析

目标:
弄清楚python队列的底层实现以及队列是否线程安全

1 主入口
class Queue:
    def __init__(self, maxsize=0):
        # 设置队列的最大容量
        self.maxsize = maxsize
        self._init(maxsize)

        # 线程锁,互斥变量
        self.mutex = threading.Lock()
        # 由锁衍生出三个条件变量
        self.not_empty = threading.Condition(self.mutex)
        self.not_full = threading.Condition(self.mutex)
        self.all_tasks_done = threading.Condition(self.mutex)

        self.unfinished_tasks = 0

    def _init(self, maxsize):
        # 初始化底层数据结构
        self.queue = deque()
        
分析:
1.1)参数分析
可以看到队列中有线程锁,对队列的操作都需要获得该锁。
队列可以设置大小,底层为双端列表collections.deque()
三个条件变量not_empty, not_full, all_tasks_done都共用这把线程锁。
抽象_init方法可被子类覆盖。

        
1.2 入队操作
class Queue:
    ...
    def put(self, item, block=True, timeout=None):
        with self.not_full: # 获取条件变量not_full
            if self.maxsize > 0:
                if not block:
                    if self._qsize() >= self.maxsize:
                        raise Full # 如果 block 是 False,并且队列已满,那么抛出 Full 异常
                elif timeout is None:
                    while self._qsize() >= self.maxsize:
                        self.not_full.wait() # 阻塞直到由剩余空间
                elif timeout < 0: # 不合格的参数值,抛出ValueError
                    raise ValueError("'timeout' must be a non-negative number")
                else:
                    endtime = time() + timeout  # 计算等待的结束时间
                    while self._qsize() >= self.maxsize:
                        remaining = endtime - time()
                        if remaining <= 0.0:
                            raise Full # 等待期间一直没空间,抛出 Full 异常
                        self.not_full.wait(remaining)
            self._put(item) # 往底层数据结构中加入一个元素
            self.unfinished_tasks += 1
            self.not_empty.notify()

    def _put(self, item):
        self.queue.append(item)
        
分析:
1.2.1)
如果 block 是 False,忽略timeout参数
若此时队列已满,则抛出 Full 异常;
若此时队列未满,则立即把元素保存到底层数据结构中;
如果 block 是 True
若 timeout 是 None 时,那么put操作可能会阻塞,直到队列中有空闲的空间(默认);
若 timeout 是非负数,则会阻塞相应时间直到队列中有剩余空间,在这个期间,如果队列中一直没有空间,抛出 Full 异常;

处理好参数逻辑后,,将元素保存到底层数据结构中,并递增unfinished_tasks,同时通知 not_empty ,唤醒在其中等待数据的线程。

1.3 出队操作
class Queue:
    ...
    def get(self, block=True, timeout=None):
        with self.not_empty:
            if not block:
                if not self._qsize():
                    raise Empty
            elif timeout is None:
                while not self._qsize():
                    self.not_empty.wait()
            elif timeout < 0:
                raise ValueError("'timeout' must be a non-negative number")
            else:
                endtime = time() + timeout
                while not self._qsize():
                    remaining = endtime - time()
                    if remaining <= 0.0:
                        raise Empty
                    self.not_empty.wait(remaining)
            item = self._get()
            self.not_full.notify()
            return item

    def _get(self):     
        return self.queue.popleft()
        
分析:
1.3.1)
如果 block 是 False,忽略timeout参数
若此时队列没有元素,则抛出 Empty 异常;
若此时队列由元素,则立即把元素保存到底层数据结构中;

如果 block 是 True
若 timeout 是 None 时,那么get操作可能会阻塞,直到队列中有元素(默认);
若 timeout 是非负数,则会阻塞相应时间直到队列中有元素,在这个期间,如果队列中一直没有元素,则抛出 Empty 异常;
最后,通过 self.queue.popleft() 将最早放入队列的元素移除,并通知 not_full ,唤醒在其中等待数据的线程

这里有个值得注意的地方,在 put() 操作中递增了 self.unfinished_tasks ,而 get() 中却没有递减,这是为什么?


这其实是为了留给用户一个消费元素的时间,get() 仅仅是获取元素,并不代表消费者线程处理的该元素,用户需要调用 task_done() 来通知队列该任务处理完成了:

1.4 任务完成
class Queue:
    ...
    def task_done(self):
        with self.all_tasks_done:
            unfinished = self.unfinished_tasks - 1
            if unfinished <= 0:
                if unfinished < 0: # 也就是成功调用put()的次数小于调用task_done()的次数时,会抛出异常
                    raise ValueError('task_done() called too many times')
                self.all_tasks_done.notify_all() # 当unfinished为0时,会通知all_tasks_done
            self.unfinished_tasks = unfinished

    def join(self):
        with self.all_tasks_done:
            while self.unfinished_tasks: # 如果有未完成的任务,将调用wait()方法等待
                self.all_tasks_done.wait()
                
分析:
1.4.1)
由于 task_done() 使用方调用的,当 task_done() 次数大于 put() 次数时会抛出异常。
task_done() 操作的作用是唤醒正在阻塞的 join() 操作。
join() 方法会一直阻塞,直到队列中所有的元素都被取出,
并被处理了(和线程的join方法类似)。也就是说 join() 方法必须配合 task_done() 来使用才行。

2 Lifo队列
LIFO 后进先出队列
LifoQueue使用后进先出顺序,与栈结构相似:

class LifoQueue(Queue):
    '''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 优先级队列
from heapq import heappush, heappop

class 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)
        
分析:
使用优先队列的时候,需要定义 __lt__ 魔术方法,来定义它们之间如何比较大小。若元素的 priority 相同,依然使用先进先出的顺序。
        
4 总结
1)python的Queue.Queue()队列时先进先出,采用线程锁,是线程安全的。
2)先进先出队列底层是collections.deque()双端队列实现。
3)先进先出队列put的原理是:
3.1)若block为False,则忽略timeout参数,队列满则抛异常,否则保存元素;
3.2)若block为True:
    3.2.1)若timeout为None,put操作阻塞直到队列中有空闲空间
    3.2.2)若timeout为非负数,则阻塞相应时间直到队列中有剩余空间。若一直没有剩余空间,则抛异常。
4)先进先出队列get原理是:
4.1)若block为False,则忽略timeout参数,队列没有元素则抛异常,否则返回元素
4.2)若block为True:
    4.2.1)若timeout为None,则阻塞直到队列中有元素
    4.2.2)若timeout为非负数,则会阻塞相应时间直到队列中有元素。
    
    
    

参考:
https://zhuanlan.zhihu.com/p/57164887
https://blog.csdn.net/qq1059752567/article/details/96308016

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值