Queue是Python标准库中的线程安全的队列(FIFO先进先出)实现, 提供了一个适用于多线程编程的先进先出的数据结构,即队列,用来在生产者和消费者线程之间的信息传递。
Python 3.7版本开始添加了新功能SimpleQueue,SimpleQueue相比Queue简化了部分高级功能,拥有更好的性能表现。
在实际应用中,SimpleQueue的相应功能已可以满足大部分日常需求,所以推荐使用。
以下为SimpleQueue所拥有的功能:
-
SimpleQueue.qsize()
返回队列的大致大小。注意,qsize() > 0 不保证后续的 get() 不被阻塞。 -
SimpleQueue.empty()
如果队列为空,返回 True ,否则返回 False 。如果 empty() 返回 False ,不保证后续调用的 get() 不被阻塞。 -
SimpleQueue.put(item, block=True, timeout=None)
将 item 放入队列。此方法永不阻塞,始终成功(除了潜在的低级错误,例如内存分配失败)。可选参数 block 和 timeout 仅仅是为了保持 Queue.put() 的兼容性而提供,其值被忽略。 -
SimpleQueue.put_nowait(item)
相当于 put(item, block=False),为保持与 Queue.put_nowait() 的兼容性而提供。 -
SimpleQueue.get(block=True, timeout=None)
从队列中移除并返回一个项目。如果可选参数 block 是 true 并且 timeout 是 None (默认值),则在必要时阻塞至项目可得到。如果 timeout 是个正数,将最多阻塞 timeout 秒,如果在这段时间内项目不能得到,将引发 Empty 异常。反之 (block 是 false) , 如果一个项目立即可得到,则返回一个项目,否则引发 Empty 异常 (这种情况下,timeout 将被忽略)。 -
SimpleQueue.get_nowait()
相当于 get(False) 。
我们下面通过一个生产者消费者模式的案例来解释下SimpleQueue的用法。
假设我们有两个线程,一个线程专门用来接收CAN工具源源不断接收到的CAN报文(生产者线程),一个专门用来解析接收到CAN报文(消费者线程)。 由于生产者线程产生的CAN报文非常密集,所以有可能导致消费者线程来不及及时处理,所以我们通过队列来做缓存,防止漏帧。
from queue import SimpleQueue
import time, threading
import sys
# 生产者线程
def product(name):
global finish_flag #需要改写变量,需要加上global
count = 1
for n in range(100000): #假设接收到了100000个报文
sys.stdout.write(f"{name} 产生的帧:{count}\n") # 打印产生的报文编号
q.put(f"{name} 产生的帧:{count}") # 将报文编号存入队列中
count += 1
finish_flag = True #所有任务完成
# 消费者线程
def consume(name):
while True:
sys.stdout.write(f"{name} 获取了:{q.get()}\n") # 获取队列中的一个报文编号,默认get不加参数,程序会处于阻塞状态,直到队列中有数据。
if finish_flag and q.empty(): #当生产者线程完成所有任务,同时队列也为空时退出
break
if __name__ == "__main__":
# 初始化报文的消息队列
q = SimpleQueue()
finish_flag = False
can_rec = threading.Thread(target=product, args=('can工具',))
can_analysis = threading.Thread(target=consume, args=('can解析器',))
# 开启线程,并等待其执行完毕。
t1=time.time()
can_rec.start()
can_analysis.start()
can_rec.join()
can_analysis.join()
print(time.time()-t1)
总结,日常应用中,对于有密集数据接收的情况,比如CAN, Lin报文,接口数据都可以使用Queue做缓存来防止数据丢失。
Queue和SimpleQueue因为其线程安全的特性,对于线程的读写是非常友好的,也不需要担心线程间的干扰。
但使用时也需要注意,一旦使用了队列,其原有数据序列间的间隔是无法保存的,比如CAN报文的周期是无法通过队列进行还原的,所以通常存入数据序列的数据我们会为其加上时间戳,以记录其原有时序。
网友评论