2010-02-09 21 views
3

J'utilise des variables de condition dans les threads qui nécessitent un timeout. Je n'ai pas remarqué jusqu'à ce que j'ai vu l'utilisation du processeur lors de l'exécution de nombreux threads, que la variable de condition fournie dans le module de thread ne dort pas réellement, mais interroge lorsqu'un délai d'attente est fourni en tant qu'argument.Existe-t-il une alternative aux variables threading.Condition dans python qui supportent mieux les timeouts sans scrutation?

Existe-t-il une alternative à cela qui dort réellement comme pthreads?

Il semble difficile d'avoir beaucoup de fils dormant à des intervalles de plusieurs secondes seulement pour avoir encore du temps CPU.

Merci!

+0

Très intéressant; Je n'avais pas utilisé la fonctionnalité de timeout des variables de condition en Python, donc je ne me suis pas rendu compte qu'elles étaient implémentées avec cette approche de sondage et de sommeil. À partir de la liste de diffusion Python-Dev, il semble que quelqu'un d'autre a remarqué cela et travaille sur un correctif (mail ici: http://mail.python.org/pipermail/python-dev/2009-November/094323.html, corrigez ici : http://bugs.python.org/issue7316) mais il semble que ce soit pour python3 (et pas encore officiellement accepté - bien qu'il ait été travaillé aussi récemment que la semaine dernière). –

Répondre

3

Cela semble difficile à faire en Python, mais voici une solution unique. Il repose sur la génération de threads supplémentaires, mais n'utilise pas l'interrogation ET garantit que le thread d'origine est éveillé dès l'expiration du délai d'attente ou dès que wait() d'origine revient.

Remarque: Le code suivant inclut un scénario de test qui teste à la fois la fin de l'attente conditionnelle due à un dépassement de délai et une notification.

from thread import start_new_thread 
from threading import Condition, Timer 

class ConditionWithoutPolling(): 
    """Implements wait() with a timeout without polling. Wraps the Condition 
    class.""" 
    def __init__(self, condition): 
     self.condition = condition 
     self.wait_timeout_condition = Condition() 

    def wait(self, timeout=None): 
     """Same as Condition.wait() but it does not use a poll-and-sleep method 
     to implement timeouts. Instead, if a timeout is requested two new 
     threads are spawned to implement a non-pol-and-wait method.""" 
     if timeout is None: 
      # just use the original implementation if no waiting is involved 
      self.condition.wait() 
      return 
     else: 
      # this new boolean will tell us whether we are done waiting or not 
      done = [False] 

      # wait on the original condition in a new thread 
      start_new_thread(self.wait_on_original, (done,)) 

      # wait for a timeout (without polling) in a new thread 
      Timer(timeout, lambda : self.wait_timed_out(done)).start() 

      # wait for EITHER of the previous threads to stop waiting 
      with self.wait_timeout_condition: 
       while not done[0]: 
        self.wait_timeout_condition.wait() 

    def wait_on_original(self, done): 
     """Waits on the original Condition and signals wait_is_over when done.""" 
     self.condition.wait() 
     self.wait_is_over(done) 

    def wait_timed_out(self, done): 
     """Called when the timeout time is reached.""" 
     # we must re-acquire the lock we were waiting on before we can return 
     self.condition.acquire() 
     self.wait_is_over(done) 

    def wait_is_over(self, done): 
     """Modifies done to indicate that the wait is over.""" 
     done[0] = True 
     with self.wait_timeout_condition: 
      self.wait_timeout_condition.notify() 

    # wrap Condition methods since it wouldn't let us subclass it ... 
    def acquire(self, *args): 
     self.condition.acquire(*args) 
    def release(self): 
     self.condition.release() 
    def notify(self): 
     self.condition.notify() 
    def notify_all(self): 
     self.condition.notify_all() 
    def notifyAll(self): 
     self.condition.notifyAll() 

def test(wait_timeout, wait_sec_before_notification): 
    import time 
    from threading import Lock 
    lock = Lock() 
    cwp = ConditionWithoutPolling(Condition(lock)) 
    start = time.time() 

    def t1(): 
     with lock: 
      print 't1 has the lock, will wait up to %f sec' % (wait_timeout,) 
      cwp.wait(wait_timeout) 
     time_elapsed = time.time() - start 
     print 't1: alive after %f sec' % (time_elapsed,)   

    # this thread will acquire the lock and then conditionally wait for up to 
    # timeout seconds and then print a message 
    start_new_thread(t1,()) 

    # wait until it is time to send the notification and then send it 
    print 'main thread sleeping (will notify in %f sec)' % (wait_sec_before_notification,) 
    time.sleep(wait_sec_before_notification) 
    with lock: 
     cwp.notifyAll() 
     print 'notification sent, will continue in 2sec' 
    time.sleep(2.0) # give the other time thread to finish before exiting 

if __name__ == "__main__": 
    print 'test wait() ending before the timeout ...' 
    test(2.0, 1.0) 

    print '\ntest wait() ending due to the timeout ...' 
    test(2.0, 4.0) 
+0

Il sera intéressant de savoir à quel point cela aide (ou pas du tout) les performances (échange hors sondage pour une création de thread supplémentaire). Je suppose que si vous sondez beaucoup, ce sera peut-être un compromis valable. Avec un peu plus de travail, peut-être que les minuteurs pourraient tous être desservis par un seul thread (si vous appelez attendre avec un délai d'attente fréquemment peut-être cela aiderait). –

+0

Une solution intéressante au problème, semble très cher créer 2 threads pour chaque condition. Je vais avoir un jeu avec ça – goji

1

Je ne suis pas familier avec Python, mais si vous êtes capable de bloquer sur une variable de condition (sans un délai d'attente), vous pouvez implémenter le délai d'expiration vous-même. Laissez le thread de blocage stocker l'heure à laquelle il a commencé à bloquer et réglez-le sur une minuterie. Quand il se réveille, vérifiez le temps écoulé pour un délai d'expiration. Ce n'est pas un très bon moyen de le faire, sauf si vous pouvez agréger les minuteurs à un seul thread, sinon, votre nombre de threads double sans raison.