web-dev-qa-db-fra.com

Utilisation de asyncio.Queue pour le flux producteur-consommateur

Je ne sais pas comment utiliser asyncio.Queue Pour un modèle producteur-consommateur particulier dans lequel le producteur et le consommateur opèrent simultanément et indépendamment.

Tout d'abord, considérons cet exemple, qui suit de près celui des documents pour 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())

Il y a un détail plus fin à propos de ce script: les éléments sont placés dans la file d'attente de manière synchrone, avec queue.put_nowait(sleep_for) sur une boucle for conventionnelle.

Mon objectif est de créer un script qui utilise async def worker() (ou consumer()) et async def producer(). Les deux doivent être planifiés pour s'exécuter simultanément. Aucune coroutine de consommateur n'est explicitement liée ou enchaînée à un producteur.

Comment puis-je modifier le programme ci-dessus afin que le (s) producteur (s) soit sa propre coroutine qui puisse être programmée simultanément avec les consommateurs/travailleurs?


Il y a un deuxième exemple de PYMOTW . Elle oblige le producteur à connaître à l'avance le nombre de consommateurs et utilise None pour signaler au consommateur que la production est terminée.

7
Brad Solomon

Comment puis-je modifier le programme ci-dessus afin que le (s) producteur (s) soit sa propre coroutine qui puisse être programmée simultanément avec les consommateurs/travailleurs?

L'exemple peut être généralisé sans changer sa logique essentielle:

  • Déplacez la boucle d'insertion vers une coroutine de production distincte.
  • Démarrez les consommateurs en arrière-plan, en les laissant traiter les articles produits.
  • Attendez que le ou les producteurs finissent par awaiting, comme avec await producer() ou await gather(*producers), etc.
  • Une fois que tous les producteurs ont terminé, attendez que les éléments produits restants soient traités avec await queue.join()
  • Annulez les consommateurs, qui attendent tous paresseusement le prochain élément en file d'attente qui n'arrivera jamais.

Voici un exemple de mise en œuvre de ce qui précède:

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())
12
user4815162342