Programing

Python time.sleep () 대 event.wait ()

lottogame 2020. 12. 2. 07:44
반응형

Python time.sleep () 대 event.wait ()


다중 스레드 Python 응용 프로그램에서 정기적으로 작업을 수행하고 싶습니다. 나는 그것을하는 두 가지 다른 방법을 보았습니다

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

또는

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

한 가지 방법이 다른 것보다 이점이 있습니까? 리소스를 적게 사용합니까, 아니면 다른 스레드와 GIL을 더 잘 사용합니까? 내 앱의 나머지 스레드가 더 반응하도록 만드는 것은 무엇입니까?

(일부 외부 이벤트 세트를 가정 exit이나 exit_flag, 및 종료하는 동안 전체 지연을 기다릴 기꺼이)


가 설정 exit_flag.wait(timeout=DELAY)되면 즉시 while 루프에서 벗어날 수 있으므로 사용 하면 반응이 더 빨라집니다 exit_flag. 를 사용 time.sleep하면 이벤트가 설정된 후에도 몇 초 동안 time.sleep잤을 때까지 통화 중에 대기하게됩니다 DELAY.

구현 측면에서 Python 2.x와 Python 3.x는 매우 다른 동작을합니다. Python 2.x에서는 Event.wait여러 작은 time.sleep호출을 사용하여 순수 Python으로 구현됩니다 .

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)

이것은 실제로 사용하는 wait것이 DELAY무조건 전체를 잠자는 것보다 CPU를 조금 더 많이 소모 한다는 것을 의미 하지만, (얼마나 오래 걸리는지에 따라 잠재적으로 많은 DELAY) 응답 성이 있다는 이점이 있습니다. 또한 GIL을 자주 재 획득해야하기 때문에 다음 절전을 예약 할 수 있고 time.sleep전체 DELAY. 이제 GIL을 더 자주 획득하면 응용 프로그램의 다른 스레드에 눈에 띄는 영향이 있습니까? 어쩌면 아닐 수도 있습니다. 실행중인 다른 스레드의 수와 보유한 작업로드의 종류에 따라 다릅니다. 내 생각에는 많은 수의 스레드가 있거나 CPU 바운드 작업을 많이 수행하는 다른 스레드가 없으면 특별히 눈에 띄지 않을 것이지만 두 가지 방법을 모두 시도하고 볼 수있을만큼 쉽습니다.

Python 3.x에서는 구현의 대부분이 순수 C 코드로 이동되었습니다.

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()

그리고 잠금을 획득하는 C 코드 :

/* 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;
}

이 구현은 응답 성이 뛰어나며 GIL을 다시 획득하는 빈번한 wakeup이 필요하지 않으므로 두 세계를 모두 활용할 수 있습니다.


파이썬 2. *
말했다 @dano처럼, event.wait 더 반응한다,
하지만 위험 할 수있는 시스템의 경우 시간이 뒤로 변경 , 그것의 기다리는 동안!
버그 # 1607041 : Condition.wait 시간 초과가 시계 변경에 실패 함

이 샘플을 참조하십시오.

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

일반적으로 action()100ms intrvall에서 호출됩니다.
그러나 시간을 변경할 때. 한 시간 동안 두 작업 사이에 한 시간의 일시 중지가 있습니다.

결론 : 시간이 변경 될 수있는 경우에는 피해야합니다. event.wait


It is interesting to note that the event.wait() method can be invoked on its own:

from threading import Event # Needed for the  wait() method
from time import sleep     

print("\n Live long and prosper!")
sleep(1)               # Conventional sleep() Method.
print("\n Just let that soak in..")   
Event().wait(3.0) # wait() Method, useable sans thread.
print("\n Make it So! = )\n")

So why -not- use wait() as an alternative to sleep() outside of multi-threading? In a word, Zen. (Of course.) Clarity of code is an important thing.

참고URL : https://stackoverflow.com/questions/29082268/python-time-sleep-vs-event-wait

반응형