目标:
弄清楚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