Python學習筆記 - queue

MADAO是不會開花的發表於2019-01-15
  • 生產者和消費者模型

    在多執行緒開發的過程中有可能遇到這樣一種情形:

    有一部分執行緒負責生產一些資料,有一部分負責處理一些資料,把製造資料的那一部分執行緒叫做生產者,處理資料的那一部分執行緒叫做消費者,如果生產者生產的資料速度大於消費者處理資料的速度,那麼生產者就必須要等待消費者處理完成才能繼續生產,同樣消費者處理資料的速度大於生產者,那消費者就要等待生產者,為了解決這個問題,於是引入了生產者和消費者模型。

    生產者和消費者模型:

    生產者 ---->   緩衝區(倉庫) ----> 消費者
    複製程式碼

    生產者和消費者不直接通訊,生產者生產的資料丟給緩衝區,消費者拿資料則是向緩衝區拿。

    可以將生產者和消費者模型想象成寄信:

    1. 發件人就是生產者,寫信
    2. 郵箱就是緩衝區,將信放入郵箱。
    3. 郵遞員從郵箱中拿出信,進行後續的處理就是消費者

    要實現這樣的模型,需要用到佇列,佇列就是生產者和消費者之間的緩衝區。

  • queues模組

    佇列,又稱為佇列(queue),是先進先出(FIFO, First-In-First-Out)的線性表。在具體應用中通常用連結串列或者陣列來實現。佇列只允許在後端(稱為rear)進行插入操作,在前端(稱為front)進行刪除操作。 - 維基百科

    queues模組實現多生產者,多消費者佇列。

    • queue.Queue(maxsize=0)

      FIFO(先進先出) 佇列的構造方法。 maxsize 是一個整數,它設定了可以放入佇列中的專案數量的上限。一旦達到此大小,插入就會阻塞,直到佇列項被消耗。如果 maxsize 小於或等於零,佇列大小是無限的。

    • queue.LifoQueue(maxsize=0)

      LIFO(後進先出) 佇列的構造方法。 maxsize 是一個整數,它設定了可以放入佇列中的專案數量的上限。一旦達到此大小,插入就會阻塞,直到佇列項被消耗。如果 maxsize 小於或等於零,佇列大小是無限的。

    • queue.PriorityQueue(maxsize=0)

      優先順序佇列的建構函式。 maxsize 是一個整數,它設定了可以放入佇列中的專案數量的上限。一旦達到此大小,插入就會阻塞,直到佇列項被消耗。如果 maxsize 小於或等於零,佇列大小是無限的。

      存放資料的格式: Queue.put((priority_number,data)),priority_number越小,優先順序越高,data代表存入的值

    通過例子看這三種佇列的不同之處:

    import queue
    
    # 先進先出
    q_FIFO = queue.Queue(3)
    q_FIFO.put(1) # 存第一個資料
    q_FIFO.put(2) # 存第二個資料
    q_FIFO.put(3) # 存第二個資料
    
    print('取第一個資料', q_FIFO.get())
    print('取第二個資料', q_FIFO.get())
    print('取第三個資料', q_FIFO.get()) 
    複製程式碼

    結果:

    Python學習筆記 - queue

    拿出的資料的順序和存放進去資料的順序是一樣的

    # 後進先出
    q_LIFO = queue.LifoQueue(3)
    
    q_LIFO.put(1)  # 存第一個資料
    q_LIFO.put(2)  # 存第二個資料
    q_LIFO.put(3)  # 存第二個資料
    
    print('取第一個資料', q_LIFO.get())
    print('取第二個資料', q_LIFO.get())
    print('取第三個資料', q_LIFO.get())
    複製程式碼

    結果:

    Python學習筆記 - queue

    越後放進去的資料越先取出來

    # 優先順序佇列
    q_Priority = queue.PriorityQueue(3)
    
    q_Priority.put((10, '我的優先順序是10'))  # 存第一個資料
    q_Priority.put((-1, '我的優先順序是-1'))  # 存第二個資料
    q_Priority.put((1, '我的優先順序是10'))  # 存第三個資料
    
    print('取第一個資料', q_Priority.get())
    print('取第二個資料', q_Priority.get())
    print('取第三個資料', q_Priority.get())
    複製程式碼

    結果:

    Python學習筆記 - queue

    它根據傳入的(priority_number,data)格式的資料中的priority_number決定優先順序的大小,priority_number越小優先順序越高。

  • Queue,LifoQueue,PriorityQueue佇列提供的方法

    • Queue.qsize()

      返回當前佇列的長度,但是結果並不可靠,原因是多執行緒情況下,有可能在Queue.qsize()拿到的結果是1,但是其他執行緒有可能在當前執行緒獲取資料之前已經拿走資料了,這就會導致當前執行緒拿資料的時候佇列的長度變成0,所以Queue.qsize的結果是不可靠的。

      例子:

      import queue
      from threading import Thread
      import time
      
      # 建立佇列
      q = queue.Queue(3)
      
      # 消費者1
      def print_something():
          q_size = q.qsize()
          if q_size:
              print('佇列長度為:%d' % q_size)
              time.sleep(1)  # 等一秒再處理資料
              print('消費者1', q.get())
              q.task_done()  # task_done方法等下會說,可理解為通知佇列當前獲取到的資料已處理完畢
      
      # 生產者1
      def put_something():
          q.put(1)
          print('生產者1')
      
      
      # 消費者2
      def early_treatment():
          print('消費者2', q.get())
          q.task_done()
      
      
      t1 = Thread(target=put_something)
      t2 = Thread(target=print_something)
      t3 = Thread(target=early_treatment)
      
      t1.start()
      t2.start()
      t3.start()
      
      t1.join()
      t2.join()
      t3.join()
      複製程式碼

      結果:

      Python學習筆記 - queue

      因為消費者1等了一秒才開始處理資料,另一個執行緒的消費者2則是立即處理資料,所以資料當消費者1處理資料的時候,佇列已經空了,沒有資料了,消費者1就只能一直等待。

    • Queue.empty()

      如果佇列為空,返回 True,否則返回 False。它和qsize一樣,結果也是不可靠的。

    • Queue.full()

      如果佇列已滿,返回 True,否則返回 False。它和qsize一樣,結果也是不可靠的。

    • Queue.put(item, block=True, timeout=None)

      將 item 放入佇列。

      • item:放入佇列的資料
      • block:是否阻塞
      • timeout:阻塞等待時間,必須是非負數或者None

      例子:

      • block為True且timeout為None,如果佇列滿了,那麼會一直等待,直到佇列有空位再進行放入。
      import queue
      import datetime
      
      # 建立佇列
      q = queue.Queue(3)
      
      # block為True且timeout為None
      try:
          for i in range(4):
              q.put(i)
              old_time = datetime.datetime.now()
      except queue.Full:
          new_time = datetime.datetime.now()
          print('阻塞等待時間:%s秒' % (new_time - old_time).seconds)
      複製程式碼
      • block為True且timeout為0,如果佇列滿了,立即丟擲queue.Full異常。
      import queue
      import datetime
      
      # 建立佇列
      q = queue.Queue(3)
      
      # block為True且timeout為0
      try:
          for i in range(4):
              q.put(i, timeout=0)
              old_time = datetime.datetime.now()
      except queue.Full:
          new_time = datetime.datetime.now()
          print('阻塞等待時間:%s秒' % (new_time - old_time).seconds)
      複製程式碼

      結果:

      Python學習筆記 - queue

      • block為True且timeout為正數,如果佇列滿了,會等待timeout中傳入的秒數,如果超過了timeout中傳入的秒數,丟擲queue.Full異常。
      import queue
      import datetime
      
      # 建立佇列
      q = queue.Queue(3)
      
      # block為True且timeout為正數
      try:
          for i in range(4):
              q.put(i, timeout=3)
              old_time = datetime.datetime.now()
      except queue.Full:
          new_time = datetime.datetime.now()
          print('阻塞等待時間:%s秒' % (new_time - old_time).seconds)
      複製程式碼

      結果:

      Python學習筆記 - queue

      • block為False,佇列滿了會立即丟擲queue.Full異常。
    • Queue.put_nowait(item)

      相當於 put(item, block=False)。

    • Queue.get(block=True, timeout=None)

      從佇列中刪除並返回專案。block和timeout引數的含義和put方法相同,不過執行的操作是拿出不是放入。當佇列為空時,繼續get會丟擲queue.Empty異常。

    • Queue.get_nowait()

      相當於 get(False)。

    • Queue.task_done()

      通知佇列當前使用get獲取的任務已完成。

    • Queue.join()

      阻塞,直到佇列中的所有任務都被獲取和處理。

  • 簡單的生產者消費者模型

    • 先生產後消費

      import threading
      import queue
      import random
      
      def producer():
          """
          模擬生產者
          """
          for i in range(10):
              q.put("%s元" % random.randint(1, 100))
      
          print("等待所有的錢被取走...")
          q.join()
          print("所有的錢被取完了...")
      
      
      def consumer(n):
          """
          模擬消費者
          """
          while q.qsize() > 0:
              print("%s 取到" % n, q.get())
              q.task_done()
      
      
      q = queue.Queue()
      
      p1 = threading.Thread(target=producer)
      p1.start()
      
      c1 = consumer("allen")
      複製程式碼
    • 邊生產邊消費

      import threading
      import queue
      import random
      import time
      
      
      def producer():
          """
          模擬生產者
          """
          while True:
              time.sleep(random.randint(1, 5))
              data = random.randint(1, 100)
              q.put("%s元" % data)
              print('存入:%s元' % data)
      
      
      def consumer(n):
          """
          模擬消費者
          """
          while True:
              time.sleep(random.randint(1, 5))
              data = q.get()
              print("%s 取到" % n, data)
              q.task_done()
      
      
      q = queue.Queue()
      names = ['allen', 'jack']
      p1 = threading.Thread(target=producer)
      p1.start()
      for name in names:
          c = threading.Thread(target=consumer, args=(name,))
          c.start()
      
      
      複製程式碼

相關文章