プロデューサーとコンシューマーの両方が同時に独立して動作する特定のプロデューサー-コンシューマーパターンに_asyncio.Queue
_を使用する方法について混乱しています。
最初に、 _asyncio.Queue
_のドキュメント の結果に密接に続くこの例を考えてみましょう。
_import asyncio
import random
import time
async def worker(name, queue):
while True:
sleep_for = await queue.get()
await asyncio.sleep(sleep_for)
queue.task_done()
print(f'{name} has slept for {sleep_for:0.2f} seconds')
async def main(n):
queue = asyncio.Queue()
total_sleep_time = 0
for _ in range(20):
sleep_for = random.uniform(0.05, 1.0)
total_sleep_time += sleep_for
queue.put_nowait(sleep_for)
tasks = []
for i in range(n):
task = asyncio.create_task(worker(f'worker-{i}', queue))
tasks.append(task)
started_at = time.monotonic()
await queue.join()
total_slept_for = time.monotonic() - started_at
for task in tasks:
task.cancel()
# Wait until all worker tasks are cancelled.
await asyncio.gather(*tasks, return_exceptions=True)
print('====')
print(f'3 workers slept in parallel for {total_slept_for:.2f} seconds')
print(f'total expected sleep time: {total_sleep_time:.2f} seconds')
if __name__ == '__main__':
import sys
n = 3 if len(sys.argv) == 1 else sys.argv[1]
asyncio.run(main())
_
このスクリプトに関する詳細がもう1つあります。アイテムは、従来のforループを介してqueue.put_nowait(sleep_for)
で同期的にキューに入れられます。
私の目標は、async def worker()
(またはconsumer()
)とasync def producer()
を使用するスクリプトを作成することです。両方を同時に実行するようにスケジュールする必要があります。消費者のコルーチンは明示的に生産者に結び付けられたり、生産者から連鎖されたりすることはありません。
プロデューサーが、コンシューマー/ワーカーと同時にスケジュールできる独自のコルーチンになるように、上記のプログラムを変更するにはどうすればよいですか?
[〜#〜] pymotw [〜#〜] の2番目の例があります。プロデューサーは事前にコンシューマーの数を知る必要があり、None
を使用して、生産が完了したことをコンシューマーに知らせます。
プロデューサーが、コンシューマー/ワーカーと同時にスケジュールできる独自のコルーチンになるように、上記のプログラムを変更するにはどうすればよいですか?
この例は、基本的なロジックを変更せずに一般化できます。
await producer()
やawait gather(*producers)
などと同様に、プロデューサーがawait
ingして終了するのを待ちます。await queue.join()
で残りのプロデュースアイテムが処理されるのを待ちます上記を実装する例を次に示します。
import asyncio, random, time
async def rnd_sleep(t):
# sleep for T seconds on average
await asyncio.sleep(t * random.random() * 2)
async def producer(queue):
while True:
token = random.random()
print(f'produced {token}')
if token < .05:
break
await queue.put(token)
await rnd_sleep(.1)
async def consumer(queue):
while True:
token = await queue.get()
await rnd_sleep(.3)
queue.task_done()
print(f'consumed {token}')
async def main():
queue = asyncio.Queue()
# fire up the both producers and consumers
producers = [asyncio.create_task(producer(queue))
for _ in range(3)]
consumers = [asyncio.create_task(consumer(queue))
for _ in range(10)]
# with both producers and consumers running, wait for
# the producers to finish
await asyncio.gather(*producers)
print('---- done producing')
# wait for the remaining tasks to be processed
await queue.join()
# cancel the consumers, which are now idle
for c in consumers:
c.cancel()
asyncio.run(main())