Partage
  • Partager sur Facebook
  • Partager sur Twitter

Limiter le temps d'exécution d'une fonction

    9 février 2015 à 9:25:54

    Bonjour, 
    Je travaille depuis peu sur un algorithme python (j'apprend python en même temps) qui permet d'étudier des fonctions mathématiques (avec l'aide de Sympy notamment). 
    Je cherche à mettre en place un système qui limite la durée d'exécution de certaines fonctions python, par exemple le calcul d'une limite d'une fonction qui serait beaucoup trop long.... 
    Après maintes recherches, j'ai trouvé ce qui suit sur le net et l'ait modifié pour mon cas.... Ca à l'air de fonctionner mais je ne sais pas si le Thread est vraiment arrêté à la fin, dans le cas où le timeout est dépassé et dans le cas ou le calcul dure moins longtemps ?!

    Voici le code : 

    def execTimeOut(func, arguments, timeout=30, default=None):
        class InterruptableThread(threading.Thread):
            def __init__(self):
                threading.Thread.__init__(self)
                self.result = default
                self.error = default
            def run(self):
                try:
                    self.result = func(*arguments)
                except:
                    self.result = default
        it = InterruptableThread()
        it.start()
        it.join(timeout)
        if it.isAlive():
            return default
        elif it.error:
            return default
        return it.result

    Ensuite, j'exécute ma_fonction avec :

    resultat = execTimeOut(ma_fonction, mes_arguments, 30, None)

    Quelqu'un saurait-il me dire si la def 'execTimeOut' est correcte et s'il y a des corrections éventuelles à apporter ? 
    Merci d'avance.
    Arnaud

    • Partager sur Facebook
    • Partager sur Twitter
      9 février 2015 à 9:51:25

      Il me semble effectivement que, bien que ta fonction execTimeOut retourne une valeur (default), ton thread n'est aucune stoppé s'il dépasse le temps indiqué. Et tuer un thread en Python n'est pas vraiment une opération courante. Cependant, il semble exister une solution par ici: https://web.archive.org/web/20130503082442/http://mail.python.org/pipermail/python-list/2004-May/281943.html

      • Partager sur Facebook
      • Partager sur Twitter
        9 février 2015 à 13:45:04

        Je lis la documentation et je vois qu'en passant un argument à join(timeout=None), on bloque le thread depuis lequel on appel join et on attend que le thread ait terminé, qu'il ait lancé une exception ou que le timeout soit passé. Voir doc

        Donc on est sûr d'arriver en ligne 15 seulement si le thread est fini ou si le timeout est passé. Les conditions permettent de vérifier dans quel cas on se trouve.

        Pour moi, ça devrait fonctionner comme désiré.

        • Partager sur Facebook
        • Partager sur Twitter
          9 février 2015 à 14:29:50

          Oui, l'OP nous indique d'ailleurs que ça fonctionne bien, join est débloqué une fois le timeout écoulé. Cependant, il est fort probable que le thread continue son exécution, ce qui n'a plus aucune utilité et consomme des ressources.

          • Partager sur Facebook
          • Partager sur Twitter
            9 février 2015 à 14:47:15

            Merci pour vos réponses.

            Effectivement la doc explique que la méthode join() se termine jusqu'à ce que le timeout en option se produise... donc ça devrait effectivement s'arrêter dans tous les cas.

            Je vais également essayer la solution de entwanne pour voir ce que ça donne.

            En tout cas, encore merci pour vos réponses.

            • Partager sur Facebook
            • Partager sur Twitter
              9 février 2015 à 17:36:06

              J'ai testé de mon côté, et en effet, bien que le join soit débloqué, la console reste ouverte en attendant que le thread soit terminé.

              Je peux te proposer une autre solution. Si tu utilises Python 3.4, il y a la nouvelle librairie asyncio qui permet de programmer dans un seul thread. Voici un code qui montre comment créer une fonction de calcul qui rend la main à chaque itération. Ca permet donc d'attendre un temps donné et d'arrêter la fonction si nécessaire.

              !/usr/bin/env python3

              -- coding: utf8 --

              import asyncio, math

              @asyncio.coroutine def wait_one_step():

              """Coroutine that completes after a BaseEventLoop step."""
              future = asyncio.futures.Future()
              result = None
              handle = future._loop.call_soon(future._set_result_unless_cancelled, result)
              try:
                  return (yield from future)
              finally:
                  handle.cancel()
              

              @asyncio.coroutine def heavy_computation(future):

              """Dummy function making a lot of computations."""
              a = 0
              for i in range(1, 1000000):
                  a += math.sqrt(i) / i
                  yield from wait_one_step()
                  
              future.set_result(a)
              

              loop = asyncio.get_event_loop() future = asyncio.futures.Future()

              try:

              task = asyncio.wait_for(heavy_computation(future), 3.0)
              loop.run_until_complete(task)
              print(future.result())
              

              except asyncio.TimeoutError:

              print("Time elapsed")
              

              finally:

              loop.close()
              
              </pre> Tu peux essayer avec différentes valeurs à la ligne 21 pour voir soit la coroutine retourner une valeur, soit le message Time elapsed apparaître.

              Ca prend un peu de temps de penser en terme de coroutine, mais ça pourrait être une approche concrète et portable pour toi.

              -
              Edité par Dan737 9 février 2015 à 17:36:17

              • Partager sur Facebook
              • Partager sur Twitter
                9 février 2015 à 17:56:46

                Merci pour ta réponse Dan737.

                C'est un peu compliqué pour moi qui débute dans le langage python... :o mais je vais essayer de le tester !

                Oui j'utilise python 3.4, je vais tenter ta solution ce soir en l'adaptant à mon algorithme. Si j'ai bien compris, il faut que je remplace la boucle 'for' par ma fonction à tester, et je suppose que le timeout est la valeur 3.0 dans les arguments de 'asyncio.wait_for()' ?

                Je vous tient au courant.

                Merci

                -
                Edité par KiRa2a 9 février 2015 à 17:57:26

                • Partager sur Facebook
                • Partager sur Twitter
                  9 février 2015 à 18:10:29

                  Oui c'est bien ça. Par contre tu dois comprendre que la fonction que tu veux y mettre doit rendre la main assez souvent. Donc s'il n'y a pas de boucles dans ta fonction, ça ne va pas être possible. S'il y a une boucle du type while ou for, il te suffit alors de placer le yield from wait_one_step() dedans pour rendre la main à la boucle principale.

                  Par contre si ta fonction est une fonction du style Numpy sans une boucle accessible (car écrite en langage C), alors cette méthode ne fonctionnera pas.

                  -
                  Edité par Dan737 9 février 2015 à 18:10:44

                  • Partager sur Facebook
                  • Partager sur Twitter
                    9 février 2015 à 19:43:29

                    Je viens de lire ta réponse et donc ce n'est pas la peine que j'essaie, car la fonction que je souhaite tester est la fonction de calculs de limites 'limit()' de Sympy !!

                    Tant pis ;(

                    Merci quand même d'avoir essayer de résoudre mon problème...

                    -
                    Edité par KiRa2a 9 février 2015 à 19:46:42

                    • Partager sur Facebook
                    • Partager sur Twitter
                    Anonyme
                      9 février 2015 à 21:04:46

                      Non testé !

                      from multiprocessing import Pool, TimeoutError
                      
                      def execTimeOut(func, args, t=30, default=None):
                          result = pool.apply_async(f, args)
                          try:
                              return result.get(timeout=t)
                          except TimeoutError:
                              return default
                      
                      pool = Pool(processes=1)
                      # execution de la fonction





                      -
                      Edité par Anonyme 9 février 2015 à 21:08:39

                      • Partager sur Facebook
                      • Partager sur Twitter

                      Limiter le temps d'exécution d'une fonction

                      × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                      × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                      • Editeur
                      • Markdown