Python queueとは?スレッド間で安全にデータを渡す方法

queueは、スレッド間でデータを安全に受け渡すための標準モジュールです。

内部でロックを持っているため、自分でLockを書かなくても競合状態が起きません。


queueの基本

import queue

q = queue.Queue()

q.put("a")
q.put("b")

print("qsize:", q.qsize(), "empty:", q.empty())
print("get:", q.get(), q.get())
print("空になった:", q.empty())

実行結果:

qsize: 2 empty: False
get: a b
空になった: True

put()で入れてget()で取り出す——先に入れたものが先に出てきます(FIFO)。


なぜリストではだめなのか

listでもappend()とpop(0)で同じことはできます。しかし問題が2つあります。

1つ目は安全性です。「空か確認してから取り出す」という2段階の処理は、その隙に他のスレッドが横取りできます。

if not items:          # ← この直後に他スレッドが取ってしまうと
    return
item = items.pop(0)    # ← IndexError

Queue.get()なら確認と取り出しが一体なので、この隙間がありません。

2つ目は「待てる」ことです。リストには「データが来るまで待つ」機能がありません。


空のときは待つ

q = queue.Queue()

q.get(timeout=0.2)

実行結果:

queue.Empty

get()はデータが来るまでブロックします。これがqueueの核心です。

ワーカースレッドはwhile True: q.get()と書くだけで、仕事が来るまで待機し、来たら即座に動き出します。CPUを無駄に使いません。

書き方 動き
q.get() 来るまで永久に待つ
q.get(timeout=1) 1秒待ってqueue.Empty
q.get_nowait() 待たずにqueue.Empty

詳しくは「Python Queueのput()とget()とは?データの出し入れと待機の仕組み」で解説しています。


maxsizeで流量を制限する

q = queue.Queue(maxsize=2)

q.put(1)
q.put(2)
print("full:", q.full())

q.put(3, timeout=0.2)

実行結果:

full: True
queue.Full

maxsizeを指定すると、いっぱいのときはput()側が待たされます。

これは生産者が速すぎて消費者が追いつかないときに効きます。制限しないとキューが際限なく伸び、メモリを食い潰します。


生産者・消費者パターン

import queue
import threading
import time

q = queue.Queue()

def worker(name):
    while True:
        item = q.get()
        if item is None:
            q.task_done()
            break
        print(f"{name} 処理 {item}")
        time.sleep (0.05)
        q.task_done()

ws = [threading.Thread(target=worker, args=(f"W{i}",)) for i in range(2)]
for w in ws:
    w.start()

for i in range(6):
    q.put(i)

q.join()
print("全タスク完了")

for _ in ws:
    q.put(None)
for w in ws:
    w.join()

実行結果:

W0 処理 0
W1 処理 1
W0 処理 2
W1 処理 3
W0 処理 4
W1 処理 5
全タスク完了

これがqueueの代表的な使い方です。

  • メインスレッドが仕事をput()する
  • ワーカーがget()して処理する
  • q.join()で全部終わるまで待つ
  • Noneを送ってワーカーを終了させる

最後のNoneは「もう仕事はない」という合図で、番兵(sentinel)と呼ばれます。ワーカーの数だけ送る点に注意してください。

join()とtask_done()は「Python Queueのtask_done()とjoin()とは?全タスク完了を待つ方法」で解説しています。


3種類のキュー

lq = queue.LifoQueue()
for x in [1, 2, 3]:
    lq.put(x)
print("Lifo:", [lq.get() for _ in range(3)])

pq = queue.PriorityQueue()
for x in [(3, "低"), (1, "高"), (2, "中")]:
    pq.put(x)
print("Priority:", [pq.get() for _ in range(3)])

実行結果:

Lifo: [3, 2, 1]
Priority: [(1, '高'), (2, '中'), (3, '低')]
クラス 取り出す順
Queue 入れた順(FIFO)
LifoQueue 後入れ先出し(スタック)
PriorityQueue 小さい値から
SimpleQueue FIFO・機能を絞って高速

PriorityQueueには(優先度, データ)のタプルを入れます。数値が小さいほど先に出ます。


プロセス間では使えない

queue.Queueはスレッド間専用です。プロセス間では共有できません。

マルチプロセスで使うならmultiprocessing.Queueを選んでください。名前は似ていますが別物です。

「マルチプロセスとは?マルチスレッドとの違いを初心者向けに解説」も参照してください。


非同期処理(asyncio)でも同様に専用のキューが必要です。「Python asyncio.Queueとは?非同期処理でデータを受け渡す方法」を参照してください。

queueのまとめ

  • スレッド間でデータを渡す——ロック不要で安全
  • get()はデータが来るまで待つ
  • maxsizeで流量を制限できる
  • 終了の合図はNoneなどの番兵を使う
  • FIFO・LIFO・優先度付きの3種類
  • プロセス間には使えない

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら