2015-03-16 23 views
35

Çok iş parçacıklı Python uygulamasında düzenli aralıklarla bir eylem gerçekleştirmek istiyorum. Ben yapmanın iki farklı şekilde gördük oPython time.sleep() vs event.wait()

exit = False 
def thread_func(): 
    while not exit: 
     action() 
     time.sleep(DELAY) 

veya

exit_flag = threading.Event() 
def thread_func(): 
    while not exit_flag.wait(timeout=DELAY): 
     action() 

tek yönlü bir avantaj diğeri üzerinde var mı? Birisi daha az kaynak kullanır mı, yoksa diğer konuları ve GIL ile daha mı iyi oynar? Uygulamamdaki kalan konular hangisini daha duyarlı hale getiriyor?

(bazı harici olay exit veya exit_flag setleri varsayalım ve kapatma sırasında tam gecikmeyi beklemek razıyım) daha duyarlı olacaktır exit_flag.wait(timeout=DELAY) kullanma

+2

kaçınmalıdır nerede 'exit' bayrağı ayarlar şifresi? 'Action() 'çağrısında mı yoksa başka bir iş parçacığında mı, yoksa bir sinyal işleyicisi tarafından mı çağrılıyor? – tdelaney

+0

python 2.x arka planda yoklama olsa bile bu durumda 'Event.wait' kullanıyorum. Uykuda, 1 saniyelik aralıkların makul derecede duyarlı ve daha az müdahaleci olduğu söylenebilir. – tdelaney

+0

Birincisi, bir şey için biraz CPU zamanı harcayacak. Event.wait'in – immibis

cevap

40

anında zaman while döngüsünün dışına kırarım, çünkü exit_flag ayarlanmıştır. time.sleep ile, etkinlik ayarlandıktan sonra bile, DELAY saniye boyunca uyuyana kadar time.sleep aramasında bekleyeceksiniz. Uygulama açısından, Python 2.x ve Python 3.x'in çok farklı davranışları vardır. olan bu aslında wait kullanmak anlamına

from time import time as _time, sleep as _sleep 

.... 
# This is inside the Condition class (Event.wait calls Condition.wait). 
def wait(self, timeout=None): 
    if not self._is_owned(): 
     raise RuntimeError("cannot wait on un-acquired lock") 
    waiter = _allocate_lock() 
    waiter.acquire() 
    self.__waiters.append(waiter) 
    saved_state = self._release_save() 
    try: # restore state no matter what (e.g., KeyboardInterrupt) 
     if timeout is None: 
      waiter.acquire() 
      if __debug__: 
       self._note("%s.wait(): got it", self) 
     else: 
      # Balancing act: We can't afford a pure busy loop, so we 
      # have to sleep; but if we sleep the whole timeout time, 
      # we'll be unresponsive. The scheme here sleeps very 
      # little at first, longer as time goes on, but never longer 
      # than 20 times per second (or the timeout time remaining). 
      endtime = _time() + timeout 
      delay = 0.0005 # 500 us -> initial delay of 1 ms 
      while True: 
       gotit = waiter.acquire(0) 
       if gotit: 
        break 
       remaining = endtime - _time() 
       if remaining <= 0: 
        break 
       delay = min(delay * 2, remaining, .05) 
       _sleep(delay) 
      if not gotit: 
       if __debug__: 
        self._note("%s.wait(%s): timed out", self, timeout) 
       try: 
        self.__waiters.remove(waiter) 
       except ValueError: 
        pass 
      else: 
       if __debug__: 
        self._note("%s.wait(%s): got it", self, timeout) 
    finally: 
     self._acquire_restore(saved_state) 

muhtemelen biraz CPU-aç sadece koşulsuz tam DELAY uyuyan daha fazla, ama vardır: Python 2.x Event.wait küçük time.sleep aramaların bir demet kullanarak saf Python uygulanan fayda (DELAY'un ne kadar uzun olduğuna bağlı olarak çok fazla) daha duyarlı. Ayrıca, GIL'in sıklıkla yeniden edinilmesi gerektiği anlamına gelir, böylece bir sonraki uyku planlanabilir, time.sleep ise GIL'i tam DELAY için serbest bırakabilir. Şimdi, GIL'i daha sık aldığınızda, uygulamanızdaki diğer iş parçacıkları üzerinde dikkat çekici bir etki var mı? Belki de belki de değil. Kaç tane iş parçacığının çalıştığına ve ne tür iş yüklerine sahip olduklarına bağlıdır. Tahminimce, çok sayıda iş parçacığına sahip olmadığınız sürece veya özellikle CPU'ya bağlı bir çok iş yapan başka bir iş parçacığınız olmadıkça, ancak her iki yolu da denemek ve görmek için yeterince kolay olmadıkça özellikle fark edilmeyecektir.

import _thread # C-module 
_allocate_lock = _thread.allocate_lock 

class Condition: 
    ... 
    def wait(self, timeout=None): 
     if not self._is_owned(): 
      raise RuntimeError("cannot wait on un-acquired lock") 
     waiter = _allocate_lock() 
     waiter.acquire() 
     self._waiters.append(waiter) 
     saved_state = self._release_save() 
     gotit = False 
     try: # restore state no matter what (e.g., KeyboardInterrupt) 
      if timeout is None: 
       waiter.acquire() 
       gotit = True 
      else: 
       if timeout > 0: 
        gotit = waiter.acquire(True, timeout) # This calls C code 
       else: 
        gotit = waiter.acquire(False) 
      return gotit 
     finally: 
      self._acquire_restore(saved_state) 
      if not gotit: 
       try: 
        self._waiters.remove(waiter) 
       except ValueError: 
        pass 

class Event: 
    def __init__(self): 
     self._cond = Condition(Lock()) 
     self._flag = False 

    def wait(self, timeout=None): 
     self._cond.acquire() 
     try: 
      signaled = self._flag 
      if not signaled: 
       signaled = self._cond.wait(timeout) 
      return signaled 
     finally: 
      self._cond.release() 

Ve kilit edinme C kodu:

Python 3.x ise

, uygulama çok saf C kodu taşınır

/* Helper to acquire an interruptible lock with a timeout. If the lock acquire 
* is interrupted, signal handlers are run, and if they raise an exception, 
* PY_LOCK_INTR is returned. Otherwise, PY_LOCK_ACQUIRED or PY_LOCK_FAILURE 
* are returned, depending on whether the lock can be acquired withing the 
* timeout. 
*/ 
static PyLockStatus 
acquire_timed(PyThread_type_lock lock, PY_TIMEOUT_T microseconds) 
{ 
    PyLockStatus r; 
    _PyTime_timeval curtime; 
    _PyTime_timeval endtime; 


    if (microseconds > 0) { 
     _PyTime_gettimeofday(&endtime); 
     endtime.tv_sec += microseconds/(1000 * 1000); 
     endtime.tv_usec += microseconds % (1000 * 1000); 
    } 


    do { 
     /* first a simple non-blocking try without releasing the GIL */ 
     r = PyThread_acquire_lock_timed(lock, 0, 0); 
     if (r == PY_LOCK_FAILURE && microseconds != 0) { 
      Py_BEGIN_ALLOW_THREADS // GIL is released here 
      r = PyThread_acquire_lock_timed(lock, microseconds, 1); 
      Py_END_ALLOW_THREADS 
     } 

     if (r == PY_LOCK_INTR) { 
      /* Run signal handlers if we were interrupted. Propagate 
      * exceptions from signal handlers, such as KeyboardInterrupt, by 
      * passing up PY_LOCK_INTR. */ 
      if (Py_MakePendingCalls() < 0) { 
       return PY_LOCK_INTR; 
      } 

      /* If we're using a timeout, recompute the timeout after processing 
      * signals, since those can take time. */ 
      if (microseconds > 0) { 
       _PyTime_gettimeofday(&curtime); 
       microseconds = ((endtime.tv_sec - curtime.tv_sec) * 1000000 + 
           (endtime.tv_usec - curtime.tv_usec)); 

       /* Check for negative values, since those mean block forever. 
       */ 
       if (microseconds <= 0) { 
        r = PY_LOCK_FAILURE; 
       } 
      } 
     } 
    } while (r == PY_LOCK_INTR); /* Retry if we were interrupted. */ 

    return r; 
} 

Bu uygulama duyarlı ve GIL'i yeniden edinen sık rastlanan uyandırmalara gerek duymaz, böylece her iki dünyanın da en iyisini elde edersiniz.

+0

, yani uyku (GECE) 'nin daha az GIL ağır olduğu anlamına mı geliyor? doğru değil de olsa? – user3012759

+0

@ user3012759 Bunu düşünürdüm, çünkü her bekleyiş içinde bekle, GIL'in yeniden kazanılmasını gerektiriyordu, burada "uyku" sadece "DELAY" ın tamamı için serbest bırakılabiliyor. – dano

+2

Bu python 2.x (3.x'te oldukça daha iyi) ve özellikle iplik sayısı arttıkça oldukça kötü. – tdelaney

3

Python söyledi @dano gibi 2. *
, event.wait,
daha duyarlı ancak sistem zaman geriye değiştirildiğinde bunun bekliyor iken , tehlikeli olabilir!

def someHandler(): 
    while not exit_flag.wait(timeout=0.100): 
     action() 

Normalde action() bir 100ms intrvall içinde adı verilecek: bug# 1607041: Condition.wait timeout fails on clock change


bu örneğe bakın.
Ancak saati değiştirdiğinizde.Bir saat sonra iki eylem arasında bir saat ara verilir.

Sonuç: o zaman değişimi olabileceğini izin verilen zaman, event.wait