[Python-checkins] python/dist/src/Lib Queue.py,1.20,1.21

tim_one at users.sourceforge.net tim_one at users.sourceforge.net
Mon Jul 12 02:45:16 CEST 2004


Update of /cvsroot/python/python/dist/src/Lib
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv32296/Lib

Modified Files:
	Queue.py 
Log Message:
Bug #788520: Queue class has logic error when non-blocking

I don't agree it had a bug (see the report), so this is *not* a candidate
for backporting, but the docs were confusing and the Queue implementation
was old enough to vote.

Rewrote put/put_nowait/get/get_nowait from scratch, to use a pair of
Conditions (not_full and not_empty), sharing a common mutex.  The code
is 1/4 the size now, and 6.25x easier to understand.  For blocking
with timeout, we also get to reuse (indirectly) the tedious timeout
code from threading.Condition.  The Full and Empty exceptions raised
by non-blocking calls are now easy (instead of nearly impossible) to
explain truthfully:  Full is raised if and only if the Queue truly
is full when the non-blocking put call checks the queue size, and
similarly for Empty versus non-blocking get.

What I don't know is whether the new implementation is slower (or
faster) than the old one.  I don't really care.  Anyone who cares
a lot is encouraged to check that.


Index: Queue.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/Queue.py,v
retrieving revision 1.20
retrieving revision 1.21
diff -C2 -d -r1.20 -r1.21
*** Queue.py	29 Jan 2004 06:37:49 -0000	1.20
--- Queue.py	12 Jul 2004 00:45:14 -0000	1.21
***************
*** 1,5 ****
  """A multi-producer, multi-consumer queue."""
  
! from time import time as _time, sleep as _sleep
  from collections import deque
  
--- 1,5 ----
  """A multi-producer, multi-consumer queue."""
  
! from time import time as _time
  from collections import deque
  
***************
*** 21,32 ****
          """
          try:
!             import thread
          except ImportError:
!             import dummy_thread as thread
          self._init(maxsize)
!         self.mutex = thread.allocate_lock()
!         self.esema = thread.allocate_lock()
!         self.esema.acquire()
!         self.fsema = thread.allocate_lock()
  
      def qsize(self):
--- 21,39 ----
          """
          try:
!             import threading
          except ImportError:
!             import dummy_threading as threading
          self._init(maxsize)
!         # mutex must be held whenever the queue is mutating.  All methods
!         # that acquire mutex must release it before returning.  mutex
!         # is shared between the two conditions, so acquiring and
!         # releasing the conditions also acquires and releases mutex.
!         self.mutex = threading.Lock()
!         # Notify not_empty whenever an item is added to the queue; a
!         # thread waiting to get is notified then.
!         self.not_empty = threading.Condition(self.mutex)
!         # Notify not_full whenever an item is removed from the queue;
!         # a thread waiting to put is notified then.
!         self.not_full = threading.Condition(self.mutex)
  
      def qsize(self):
***************
*** 62,110 ****
          is ignored in that case).
          """
!         if block:
              if timeout is None:
!                 # blocking, w/o timeout, i.e. forever
!                 self.fsema.acquire()
!             elif timeout >= 0:
!                 # waiting max. 'timeout' seconds.
!                 # this code snipped is from threading.py: _Event.wait():
!                 # 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).
!                 delay = 0.0005 # 500 us -> initial delay of 1 ms
                  endtime = _time() + timeout
!                 while True:
!                     if self.fsema.acquire(0):
!                         break
                      remaining = endtime - _time()
!                     if remaining <= 0:  #time is over and no slot was free
                          raise Full
!                     delay = min(delay * 2, remaining, .05)
!                     _sleep(delay)       #reduce CPU usage by using a sleep
!             else:
!                 raise ValueError("'timeout' must be a positive number")
!         elif not self.fsema.acquire(0):
!             raise Full
!         self.mutex.acquire()
!         release_fsema = True
!         try:
!             was_empty = self._empty()
              self._put(item)
!             # If we fail before here, the empty state has
!             # not changed, so we can skip the release of esema
!             if was_empty:
!                 self.esema.release()
!             # If we fail before here, the queue can not be full, so
!             # release_full_sema remains True
!             release_fsema = not self._full()
          finally:
!             # Catching system level exceptions here (RecursionDepth,
!             # OutOfMemory, etc) - so do as little as possible in terms
!             # of Python calls.
!             if release_fsema:
!                 self.fsema.release()
!             self.mutex.release()
  
      def put_nowait(self, item):
--- 69,92 ----
          is ignored in that case).
          """
!         if not block:
!             return self.put_nowait(item)
!         self.not_full.acquire()
!         try:
              if timeout is None:
!                 while self._full():
!                     self.not_full.wait()
!             else:
!                 if timeout < 0:
!                     raise ValueError("'timeout' must be a positive number")
                  endtime = _time() + timeout
!                 while self._full():
                      remaining = endtime - _time()
!                     if remaining < 0.0:
                          raise Full
!                     self.not_full.wait(remaining)
              self._put(item)
!             self.not_empty.notify()
          finally:
!             self.not_full.release()
  
      def put_nowait(self, item):
***************
*** 114,118 ****
          Otherwise raise the Full exception.
          """
!         return self.put(item, False)
  
      def get(self, block=True, timeout=None):
--- 96,108 ----
          Otherwise raise the Full exception.
          """
!         self.not_full.acquire()
!         try:
!             if self._full():
!                 raise Full
!             else:
!                 self._put(item)
!                 self.not_empty.notify()
!         finally:
!             self.not_full.release()
  
      def get(self, block=True, timeout=None):
***************
*** 127,173 ****
          in that case).
          """
!         if block:
              if timeout is None:
!                 # blocking, w/o timeout, i.e. forever
!                 self.esema.acquire()
!             elif timeout >= 0:
!                 # waiting max. 'timeout' seconds.
!                 # this code snipped is from threading.py: _Event.wait():
!                 # 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).
!                 delay = 0.0005 # 500 us -> initial delay of 1 ms
                  endtime = _time() + timeout
!                 while 1:
!                     if self.esema.acquire(0):
!                         break
                      remaining = endtime - _time()
!                     if remaining <= 0:  #time is over and no element arrived
                          raise Empty
!                     delay = min(delay * 2, remaining, .05)
!                     _sleep(delay)       #reduce CPU usage by using a sleep
!             else:
!                 raise ValueError("'timeout' must be a positive number")
!         elif not self.esema.acquire(0):
!             raise Empty
!         self.mutex.acquire()
!         release_esema = True
!         try:
!             was_full = self._full()
              item = self._get()
!             # If we fail before here, the full state has
!             # not changed, so we can skip the release of fsema
!             if was_full:
!                 self.fsema.release()
!             # Failure means empty state also unchanged - release_esema
!             # remains True.
!             release_esema = not self._empty()
          finally:
!             if release_esema:
!                 self.esema.release()
!             self.mutex.release()
!         return item
  
      def get_nowait(self):
--- 117,141 ----
          in that case).
          """
!         if not block:
!             return self.get_nowait()
!         self.not_empty.acquire()
!         try:
              if timeout is None:
!                 while self._empty():
!                     self.not_empty.wait()
!             else:
!                 if timeout < 0:
!                     raise ValueError("'timeout' must be a positive number")
                  endtime = _time() + timeout
!                 while self._empty():
                      remaining = endtime - _time()
!                     if remaining < 0.0:
                          raise Empty
!                     self.not_empty.wait(remaining)
              item = self._get()
!             self.not_full.notify()
!             return item
          finally:
!             self.not_empty.release()
  
      def get_nowait(self):
***************
*** 177,181 ****
          raise the Empty exception.
          """
!         return self.get(False)
  
      # Override these methods to implement other queue organizations
--- 145,158 ----
          raise the Empty exception.
          """
!         self.not_empty.acquire()
!         try:
!             if self._empty():
!                 raise Empty
!             else:
!                 item = self._get()
!                 self.not_full.notify()
!                 return item
!         finally:
!             self.not_empty.release()
  
      # Override these methods to implement other queue organizations



More information about the Python-checkins mailing list