2

Considérez le code suivant (explication ci-dessous):Python asyncio conditions de course protocole

import asyncio 

class MyClass(object): 
    def __init__(self): 
     self.a = 0 
    def incr(self, data): 
     self.a += 1 
     print(self.a) 

class GenProtocol(asyncio.SubprocessProtocol): 
    def __init__(self, handler, exit_future): 
     self.exit_future = exit_future 
     self.handler = handler 

    def pipe_data_received(self, fd, data): 
     if fd == 1: 
      self.handler(data) 
     else: 
      print('An error occurred') 

    def process_exited(self): 
     self.exit_future.set_result(True) 

def start_proc(stdout_handler, *command): 
    loop = asyncio.get_event_loop() 
    exit_f = asyncio.Future(loop=loop) 
    subpr = loop.subprocess_exec(lambda: GenProtocol(stdout_handler, exit_f), 
           *command, 
           stdin=None) 
    transport, protocol = loop.run_until_complete(subpr) 

    @asyncio.coroutine 
    def waiter(exit_future): 
     yield from exit_future 

    return waiter, exit_f 

def main(): 
    my_instance = MyClass() 
    loop = asyncio.get_event_loop() 
    waiter, exit_f = start_proc(my_instance.incr, 'bash', 'myscript.sh') 
    loop.run_until_complete(waiter(exit_f)) 
    loop.close() 

if __name__ == '__main__': 
    main() 

Une brève explication des componets est la suivante:

  1. MyClass est très simple
  2. GenProtocol est un class qui permet de spécifier un gestionnaire personnalisé pour les données reçues sur un stdout de sous-processus.
  3. start_proc vous permet de démarrer un processus personnalisé spécifiant un gestionnaire personnalisé pour les données reçues sur la sortie standard, via GenProtocol
  4. my_proc est un processus qui fonctionne toujours, l'envoi de données à la conduite parfois arbitraire

Maintenant, mon question est la suivante: puisque j'utilise une méthode comme gestionnaire et que cette méthode modifie un attribut d'instance de manière non atomique, est-ce potentiellement dangereux? Par exemple, lorsque je reçois de manière asynchrone des données sur le canal du sous-processus, le gestionnaire est-il appelé deux fois simultanément (risquant ainsi de corrompre les données dans MyClass.a) ou est-il sérialisé (la seconde fois que le gestionnaire est appelé le premier est fait)?

+0

Vous pouvez utiliser les primitives de synchronisation (par exemple verrouillage) https://docs.python.org/3/library/asyncio-sync.html – kwarunek

+0

Nous vous remercions de le pointeur! Donc, la réponse est oui, il peut y avoir des conditions de course, n'est-ce pas? –

+0

Voir cet excellent article sur le multi-threading, la programmation asynchrone et le raisonnement local en python: [Unyielding] (https://glyph.twistedmatrix.com/2014/02/unyielding.html). – Vincent

Répondre

4

Les méthodes de protocole sont des fonctions régulières, pas des coroutines. Ils n'ont pas limite élastique à l'intérieur. Donc, l'ordre d'exécution est assez simple: tous les appels sont sérialisés, les conditions de course ne sont pas possibles.

UPD

Dans l'exemple pipe_data_received() n'est pas un coroutine mais juste une fonction sans await/yield from à l'intérieur. Exécute toujours le tout en même temps sans aucun changement de contexte au milieu.

Vous pouvez penser que pipe_data_received() est protégé par un verrou mais en réalité aucun verrou n'est requis pour le boîtier.

Serrures sont obligatoires lorsque vous avez un coroutine comme ceci:

async def incr(self): 
    await asyncio.sleep(0.1) 
    self.counter +=1 

Dans ce dernier incr() est un coroutine et, en outre, changement de contexte est très possible sur sleep() appel. Si vous voulez protéger parallèle, vous pouvez utiliser incrémenter asyncio.Lock():

def __init__(self): 
    self.counter = 0 
    self.lock = asyncio.Lock() 

async def incr(self): 
    async with self._lock: 
     await asyncio.sleep(0.1) 
     self.counter +=1 
+0

Merci beaucoup pour votre réponse! Pouvez-vous développer un peu à ce sujet ou fournir une référence externe qui donne une explication plus détaillée sur comment cela fonctionne? –

+0

J'ai essayé d'expliquer en quelques mots de plus. N'hésitez pas à demander à nouveau que la réponse n'est toujours pas assez claire. –