Python 队列 Queue

Python的queue模块中提供了同步的、线程安全的队列类。

FIFO(先进先出) 队列 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()

 

posted on 2023-04-14 18:14  木去  阅读(199)  评论(0)    收藏  举报