web-dev-qa-db-fra.com

Subprocess.call non bloquant

J'essaie de faire un appel de sous-processus non bloquant pour exécuter un script slave.py à partir de mon programme main.py. J'ai besoin de passer des arguments de main.py à slave.py une fois quand il (slave.py) est démarré pour la première fois via subprocess.call après que ce slave.py s'exécute pendant une période de temps puis se termine.

main.py
for insert, (list) in enumerate(list, start =1):

    sys.args = [list]
    subprocess.call(["python", "slave.py", sys.args], Shell = True)


{loop through program and do more stuff..}

Et mon script esclave

slave.py
print sys.args
while True:
    {do stuff with args in loop till finished}
    time.sleep(30)

Actuellement, slave.py empêche main.py d'exécuter le reste de ses tâches, je veux simplement que slave.py soit indépendant de main.py, une fois que je lui ai passé des arguments. Les deux scripts n'ont plus besoin de communiquer.

J'ai trouvé quelques messages sur le net à propos de subprocess.call non bloquant mais la plupart d'entre eux sont centrés sur la nécessité de communiquer avec slave.py à un moment donné dont je n'ai actuellement pas besoin. Quelqu'un sait-il comment mettre cela en œuvre de manière simple ...?

42
DavidJB

Tu devrais utiliser subprocess.Popen au lieu de subprocess.call.

Quelque chose comme:

subprocess.Popen(["python", "slave.py"] + sys.argv[1:])

Depuis les documents sur subprocess.call :

Exécutez la commande décrite par args. Attendez la fin de la commande , puis renvoyez l'attribut returncode.

(N'utilisez pas non plus de liste pour passer les arguments si vous allez utiliser Shell = True).


Voici un MCVE1 exemple qui illustre un appel de suprocessus non bloquant:

import subprocess
import time

p = subprocess.Popen(['sleep', '5'])

while p.poll() is None:
    print('Still sleeping')
    time.sleep(1)

print('Not sleeping any longer.  Exited with returncode %d' % p.returncode)

Une approche alternative qui repose sur des modifications plus récentes du langage python pour permettre le parallélisme basé sur la co-routine) est:

# python3.5 required but could be modified to work with python3.4.
import asyncio

async def do_subprocess():
    print('Subprocess sleeping')
    proc = await asyncio.create_subprocess_exec('sleep', '5')
    returncode = await proc.wait()
    print('Subprocess done sleeping.  Return code = %d' % returncode)

async def sleep_report(number):
    for i in range(number + 1):
        print('Slept for %d seconds' % i)
        await asyncio.sleep(1)

loop = asyncio.get_event_loop()

tasks = [
    asyncio.ensure_future(do_subprocess()),
    asyncio.ensure_future(sleep_report(5)),
]

loop.run_until_complete(asyncio.gather(*tasks))
loop.close()

1Testé sur OS-X en utilisant python2.7 et python3.6

42
mgilson

Il y a trois niveaux de minutie ici.

Comme le dit mgilson, si vous échangez simplement subprocess.call pour subprocess.Popen, en gardant tout le reste identique, alors main.py n'attendra pas la fin de slave.py avant de continuer. Cela peut suffire en soi. Si vous vous souciez de processus zombies traîner, vous devez enregistrer l'objet renvoyé par subprocess.Popen et à un moment ultérieur appeler sa méthode wait. (Les zombies disparaîtront automatiquement à la sortie de main.py, ce n'est donc un problème grave que si main.py s'exécute pendant très longtemps et/ou peut créer de nombreux sous-processus.) Et enfin, si vous ne voulez pas de zombie mais vous ne voulez pas non plus décider où faire l'attente (cela peut être approprié si les deux processus s'exécutent pendant une période longue et imprévisible par la suite), utilisez la bibliothèque python-daemon pour que l'esclave se dissocie lui-même du maître - dans ce cas, vous pouvez continuer à utiliser subprocess.call dans le maître.

22
zwol