Python 队列 Queue
Python的queue模块中提供了同步的、线程安全的队列类。
LIFO(后进先出) LifoQueue
1.队列的常用方法
队列的常用方法有:
- qsize() :返回队列的大小。
- empty() :判断队列是否为空,为空则返回True,不为空返回False。
- full() :判断队列是否满了,满了返回True,未满返回False。
- get() :先进先出队列中,该方法是从队列中获取先插入的元素,取出后队列中就没有了该元素。如果是后进先出队列,则取出后插入的元素。
- put() :将数据放入队列的最后。
示例1:
from queue import Queue q = Queue() # 创建1个队列。如果创建时传入参数,如Queue(5),则表示创建一个容量为5的队列 # 往队列中依次放入1,2,3,4 q.put(1) q.put(2) q.put(3) q.put(4) # 查看队列大小,即队列中存放的元素个数 print(q.qsize()) # 4 print(q.get()) # 1 取出一个元素,并打印取出的元素,先进先出队列,所以第一次取出的是最先放入的1 print(q.qsize()) # 3 已取出一个元素,队列中还有3个元素 print(q.get()) # 2 最先放入的1已经被取出,剩余的元素中最先放入的是2 print(q.get()) # 3
2.队列阻塞
先看示例:
from queue import Queue q = Queue(5) for n in range(10): q.put(n) print("放入%s" % n)
# 放入0
# 放入1
# 放入2
# 放入3
# 放入4
上述示例中设置队列的容量为5,for循环往队列中放入5个元素后,队列已满,不能再放入,进入阻塞状态,程序不会继续执行。
同样,如果队列中所有元素都被取出(即队列为空),再次进去get()取出操作,也一样会进入阻塞状态。
进入阻塞状态后,程序不会继续执行。如果是取出元素时进入阻塞,程序等到队列中有元素时就会继续执行;如果是放入元素时阻塞,等从队列中取出元素后会继续执行。
可以使用full()和empty()方法先判断队列是否已满或是否为空,避免出现阻塞。
from queue import Queue q = Queue(3) for n in range(5): if not q.full(): q.put(n) else: print("队列已满") break while not q.empty(): print(q.get()) # 队列已满 # 0 # 1 # 2
阻塞可以通过block参数来控制,进行put() 或 get() 操作时,可以传入 block=False,如果出现阻塞会抛出异常。
q = Queue(1) q.put('a', block=False) q.put("b", block=False) # 队列已满,再放入元素会抛出异常 queue.Full
q = Queue(1) q.put('a', block=False) print(q.get(block=False)) # a print(q.get(block=False)) # 队列为空,取出操作会抛出异常 _queue.Empty
from queue import Queue q = Queue(1) for n in range(3): try: q.put(n, block=False) print("放入:%s" % n) except: print("队列已满") break # 放入:0 # 队列已满
timeout 参数可以控制阻塞时间,如 put(数据, timeout=3) 或 get(timeout=3) 是设置阻塞时间为3秒,3秒过后如果还阻塞则抛出异常终止线程。示例见 生产者消费者
3.Queue 在多线程中的使用
Queue是线程安全的队列,在使用时无须加锁,可以再多线程当中直接使用。队列也是实现线程间同步的一种方式。
先看普通方式的多线程:
示例3.1
import threading, time import random def put_data(li): while True: time.sleep(0.01) li.append(random.randint(1, 10)) def get_data(li): while True: time.sleep(0.01) print(li.pop()) if __name__ == '__main__': li = [] t1 = threading.Thread(target=put_data, args=(li,)) t2 = threading.Thread(target=get_data, args=(li,)) t1.start() t2.start()
上述代码,t1往列表中存入数据,t2从列表中删除数据,当列表li为空时,t2从列表中删除数据就会抛出异常。由于程序执行速度极快,上述代码可能放入1个数字就抛出异常,或者直接就抛出异常。
使用队列的代码如下:
示例3.2
from queue import Queue import random, time, threading def put_data(queue): while True: time.sleep(0.01) queue.put(random.randint(1, 10)) print("队列大小为:", queue.qsize()) def get_data(queue): while True: time.sleep(0.01) print(queue.get()) if __name__ == '__main__': q = Queue() t1 = threading.Thread(target=put_data, args=(q,)) t2 = threading.Thread(target=get_data, args=(q,)) t1.start() t2.start()
使用队列后,上述代码会一直执行,因为当队列为空进行取出操作时,t2线程进入阻塞,t1线程会继续执行,而t1线程执行后,队列中有了元素,t2可以进行取出操作。
由于程序执行速度极快,sleep的0.01秒中可以执行很多次,所以上述代码执行结果表现出放入和取出交替执行。
如果将上述代码的get()方法设置参数 block=False,那么线程t2执行的get_data很快会抛出异常停止执行,而由于队列没设容量上限,t1线程的put_data会不停往队列中放入数字。
4.生产者和消费者 以及timeout参数使用
生产者和消费者问题是线程模型中的经典模式。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
所以生产者和消费者模式包含:生产者,消费者,缓冲区。
- 生产者:往缓冲区放入数据,如果缓冲区满了,则进入阻塞状态,待缓冲区有数据被取出才会继续放入数据;
- 消费者:从缓冲区取出数据,如果缓冲区为空,则进入阻塞状态,待缓冲区被放入数据才会继续取出数据;
- 缓冲区:生产者和消费者共享数据的空间,可以理解为一个仓库,生产者往仓库放入产品,消费者取出产品。
示例3.2的代码就是一个生产者和消费者模式:put_data()是生产者,往缓冲区queue放入产品,get_data()是消费者,从缓冲区queue中取出产品。
示例3.2的代码,生产者和消费者都是死循环;
实际使用场景中,生产者往往不可能一直生产产品放入;
那么消费者就需要判断是否还有产品可拿,不能因没有产品一直处于阻塞状态,此时可以使用timeout参数设置超时时间,一定时间获取不到数据则抛出异常;
如果捕获到异常,则退出循环。
示例4.1:
from queue import Queue import random, time, threading def put_data(queue): for i in range(100): time.sleep(0.01) queue.put(random.randint(1, 10)) print("队列大小为:", queue.qsize()) def get_data(queue): while True: try: # 设置超时时间为3秒 print("取出数字:", queue.get(timeout=3)) except: print("生产的数据已经取完") break if __name__ == '__main__': q = Queue() t1 = threading.Thread(target=put_data, args=(q,)) t2 = threading.Thread(target=get_data, args=(q,)) t1.start() t2.start()
浙公网安备 33010602011771号