En python, pour un exemple de jouet:
for x in range(0, 3):
# call function A(x)
Je souhaite continuer la boucle for si la fonction A prend plus de 5 secondes en la sautant pour ne pas me coincer ou perdre du temps.
En faisant une recherche, j'ai réalisé que le sous-processus ou le fil pouvait aider, mais je ne sais pas comment le mettre en œuvre ici . Toute aide sera utile . Merci
Je pense que créer un nouveau processus peut être exagéré. Si vous utilisez un système Mac ou Unix, vous devriez pouvoir utiliser signal.SIGALRM pour forcer l'expiration des fonctions trop longues. Cela fonctionnera avec des fonctions inactives pour des problèmes de réseau ou d'autres problèmes que vous ne pouvez absolument pas gérer en modifiant votre fonction. J'ai un exemple d'utilisation dans cette réponse:
https://stackoverflow.com/a/24921763/3803152
Je modifie ma réponse ici, bien que je ne sois pas sûr de pouvoir le faire:
import signal
class TimeoutException(Exception): # Custom exception class
pass
def timeout_handler(signum, frame): # Custom signal handler
raise TimeoutException
# Change the behavior of SIGALRM
signal.signal(signal.SIGALRM, timeout_handler)
for i in range(3):
# Start the timer. Once 5 seconds are over, a SIGALRM signal is sent.
signal.alarm(5)
# This try/except loop ensures that
# you'll catch TimeoutException when it's sent.
try:
A(i) # Whatever your function that might hang
except TimeoutException:
continue # continue the for loop if function A takes more than 5 second
else:
# Reset the alarm
signal.alarm(0)
Cela définit essentiellement une minuterie pendant 5 secondes, puis tente d'exécuter votre code. Si elle ne se termine pas avant la fin du temps imparti, un SIGALRM est envoyé, que nous capturons et transformons en une exception TimeoutException. Cela vous oblige au bloc sauf, où votre programme peut continuer.
EDIT: whoops, TimeoutException
est une classe, pas une fonction. Merci, abarnert!
Les commentaires sont corrects en ce que vous devriez vérifier à l'intérieur. Voici une solution potentielle. Notez qu'une fonction asynchrone (en utilisant un thread par exemple) est différente de cette solution. C'est synchrone, ce qui signifie qu'il fonctionnera toujours en série.
import time
for x in range(0,3):
someFunction()
def someFunction():
start = time.time()
while (time.time() - start < 5):
# do your normal function
return;
Si vous pouvez interrompre votre travail et vérifier de temps en temps, c'est presque toujours la meilleure solution. Mais parfois, cela n’est pas possible - par exemple, vous lisez peut-être un fichier sur un partage de fichiers lent qui, de temps à autre, reste suspendu pendant 30 secondes. Pour gérer cela en interne, vous devez restructurer tout votre programme autour d'une boucle asynchrone d'E/S.
Si vous n'avez pas besoin d'être multiplate-forme, vous pouvez utiliser des signaux sur * nix (y compris Mac et Linux), des APC sous Windows, etc. Mais si vous devez être multiplate-forme, cela ne fonctionne pas.
Donc, si vous avez vraiment besoin de le faire simultanément, vous pouvez le faire, et parfois vous devez le faire. Dans ce cas, vous souhaiterez probablement utiliser un processus pour cela, pas un thread. Vous ne pouvez pas vraiment tuer un thread en toute sécurité, mais vous pouvez tuer un processus et il peut être aussi sûr que vous le souhaitez. De plus, si le thread prend plus de 5 secondes parce qu'il est lié au processeur, vous ne voulez pas vous battre avec le GIL.
Il y a deux options de base ici.
Tout d'abord, vous pouvez insérer le code dans un autre script et l'exécuter avec subprocess
:
subprocess.check_call([sys.executable, 'other_script.py', arg, other_arg],
timeout=5)
Comme cela passe par des canaux de processus enfants normaux, la seule communication que vous pouvez utiliser est une chaîne argv
, une valeur de retour succès/échec (en réalité un petit entier, mais ce n'est pas beaucoup mieux), et éventuellement un bloc de texte qui entre et un morceau de texte qui sort.
Vous pouvez également utiliser multiprocessing
pour générer un processus enfant de type thread:
p = multiprocessing.Process(func, args)
p.start()
p.join(5)
if p.is_alive():
p.terminate()
Comme vous pouvez le constater, c'est un peu plus compliqué, mais c'est mieux à quelques égards:
Le gros problème de tout type de parallélisme est le partage de données modifiables, par exemple, le fait de mettre à jour un dictionnaire global dans le cadre de son travail (ce que vos commentaires disent que vous essayez de faire). Avec les threads, vous pouvez en quelque sorte vous en sortir, mais les conditions de concurrence peuvent conduire à des données corrompues, vous devez donc être très prudent avec le verrouillage. Avec les processus enfants, vous ne pouvez pas vous en sortir. (Oui, vous pouvez utiliser la mémoire partagée en tant que Etat de partage entre processus explique, mais cela se limite à des types simples tels que des nombres, des tableaux fixes et des types que vous savez définir en tant que structures C, et cela vous ramène à la vie. aux mêmes problèmes que les discussions.)
Idéalement, vous organisez les choses de sorte que vous n’ayez pas besoin de partager de données pendant l’exécution du processus; vous transmettez une valeur dict
en tant que paramètre et vous obtenez une valeur dict
en retour. Cela est généralement assez facile à organiser lorsque vous avez une fonction précédemment synchrone que vous souhaitez mettre en arrière-plan.
Mais que se passe-t-il si, par exemple, un résultat partiel vaut mieux que pas de résultat? Dans ce cas, la solution la plus simple consiste à transmettre les résultats sur une file d'attente. Vous pouvez le faire avec une file d'attente explicite, comme expliqué dans Echange d'objets entre processus , mais il existe un moyen plus simple.
Si vous pouvez diviser le processus monolithique en tâches distinctes, une pour chaque valeur (ou groupe de valeurs) que vous souhaitez conserver dans le dictionnaire, vous pouvez les planifier sur une Pool
— ou, mieux encore, un concurrent.futures.Executor
. (Si vous utilisez Python 2.x ou 3.1, consultez le rapport arrière futures
sur PyPI.)
Disons que votre fonction lente ressemble à ceci:
def spam():
global d
for meat in get_all_meats():
count = get_meat_count(meat)
d.setdefault(meat, 0) += count
Au lieu de cela, vous feriez ceci:
def spam_one(meat):
count = get_meat_count(meat)
return meat, count
with concurrent.futures.ProcessPoolExecutor(max_workers=1) as executor:
results = executor.map(spam_one, get_canned_meats(), timeout=5)
for (meat, count) in results:
d.setdefault(meat, 0) += count
Tous les résultats obtenus en moins de 5 secondes sont ajoutés au dict; si ce n'est pas tout, le reste est abandonné et une TimeoutError
est levée (que vous pouvez gérer comme vous le souhaitez - enregistrez-le, créez un code de secours rapide, peu importe).
Et si les tâches sont vraiment indépendantes (comme dans mon stupide petit exemple, mais bien sûr, elles peuvent ne pas être dans votre vrai code, du moins sans une refonte majeure), vous pouvez paralléliser le travail gratuitement en supprimant simplement ce max_workers=1
. Ensuite, si vous l'exécutez sur une machine à 8 cœurs, il lancera 8 ouvriers et leur donnera chaque 1/8 du travail à faire, et les choses se feront plus rapidement. (Habituellement pas 8 fois aussi vite, mais souvent 3 à 6 fois aussi vite, ce qui est quand même assez joli.)
Cela semble être une meilleure idée (désolé, je ne suis pas encore sûr du nom de la chose en python):
import signal
def signal_handler(signum, frame):
raise Exception("Timeout!")
signal.signal(signal.SIGALRM, signal_handler)
signal.alarm(3) # Three seconds
try:
for x in range(0, 3):
# call function A(x)
except Exception, msg:
print "Timeout!"
signal.alarm(0) # reset
Peut-être que quelqu'un trouvera ce décorateur utile, basé sur la réponse de TheSoundDefense:
import time
import signal
class TimeoutException(Exception): # Custom exception class
pass
def break_after(seconds=2):
def timeout_handler(signum, frame): # Custom signal handler
raise TimeoutException
def function(function):
def wrapper(*args, **kwargs):
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(seconds)
try:
res = function(*args, **kwargs)
signal.alarm(0) # Clear alarm
return res
except TimeoutException:
print u'Oops, timeout: %s sec reached.' % seconds, function.__name__, args, kwargs
return
return wrapper
return function
tester:
@break_after(3)
def test(a,b,c):
return time.sleep(10)
>>> test(1,2,3)
Oops, timeout: 3 sec reached. test (1, 2, 3) {}