Skip to content

Commit 94e1696

Browse files
authored
bpo-14976: Reentrant simple queue (#3346)
Add a queue.SimpleQueue class, an unbounded FIFO queue with a reentrant C implementation of put().
1 parent 5ec0fee commit 94e1696

File tree

12 files changed

+1125
-12
lines changed

12 files changed

+1125
-12
lines changed

Doc/library/queue.rst

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,14 @@ the first retrieved (operating like a stack). With a priority queue,
2323
the entries are kept sorted (using the :mod:`heapq` module) and the
2424
lowest valued entry is retrieved first.
2525

26-
Internally, the module uses locks to temporarily block competing threads;
27-
however, it is not designed to handle reentrancy within a thread.
26+
Internally, those three types of queues use locks to temporarily block
27+
competing threads; however, they are not designed to handle reentrancy
28+
within a thread.
29+
30+
In addition, the module implements a "simple"
31+
:abbr:`FIFO (first-in, first-out)` queue type where
32+
specific implementations can provide additional guarantees
33+
in exchange for the smaller functionality.
2834

2935
The :mod:`queue` module defines the following classes and exceptions:
3036

@@ -67,6 +73,14 @@ The :mod:`queue` module defines the following classes and exceptions:
6773
priority: int
6874
item: Any=field(compare=False)
6975

76+
.. class:: SimpleQueue()
77+
78+
Constructor for an unbounded :abbr:`FIFO (first-in, first-out)` queue.
79+
Simple queues lack advanced functionality such as task tracking.
80+
81+
.. versionadded:: 3.7
82+
83+
7084
.. exception:: Empty
7185

7286
Exception raised when non-blocking :meth:`~Queue.get` (or
@@ -201,6 +215,60 @@ Example of how to wait for enqueued tasks to be completed::
201215
t.join()
202216

203217

218+
SimpleQueue Objects
219+
-------------------
220+
221+
:class:`SimpleQueue` objects provide the public methods described below.
222+
223+
.. method:: SimpleQueue.qsize()
224+
225+
Return the approximate size of the queue. Note, qsize() > 0 doesn't
226+
guarantee that a subsequent get() will not block.
227+
228+
229+
.. method:: SimpleQueue.empty()
230+
231+
Return ``True`` if the queue is empty, ``False`` otherwise. If empty()
232+
returns ``False`` it doesn't guarantee that a subsequent call to get()
233+
will not block.
234+
235+
236+
.. method:: SimpleQueue.put(item, block=True, timeout=None)
237+
238+
Put *item* into the queue. The method never blocks and always succeeds
239+
(except for potential low-level errors such as failure to allocate memory).
240+
The optional args *block* and *timeout* are ignored and only provided
241+
for compatibility with :meth:`Queue.put`.
242+
243+
.. impl-detail::
244+
This method has a C implementation which is reentrant. That is, a
245+
``put()`` or ``get()`` call can be interrupted by another ``put()``
246+
call in the same thread without deadlocking or corrupting internal
247+
state inside the queue. This makes it appropriate for use in
248+
destructors such as ``__del__`` methods or :mod:`weakref` callbacks.
249+
250+
251+
.. method:: SimpleQueue.put_nowait(item)
252+
253+
Equivalent to ``put(item)``, provided for compatibility with
254+
:meth:`Queue.put_nowait`.
255+
256+
257+
.. method:: SimpleQueue.get(block=True, timeout=None)
258+
259+
Remove and return an item from the queue. If optional args *block* is true and
260+
*timeout* is ``None`` (the default), block if necessary until an item is available.
261+
If *timeout* is a positive number, it blocks at most *timeout* seconds and
262+
raises the :exc:`Empty` exception if no item was available within that time.
263+
Otherwise (*block* is false), return an item if one is immediately available,
264+
else raise the :exc:`Empty` exception (*timeout* is ignored in that case).
265+
266+
267+
.. method:: SimpleQueue.get_nowait()
268+
269+
Equivalent to ``get(False)``.
270+
271+
204272
.. seealso::
205273

206274
Class :class:`multiprocessing.Queue`
@@ -210,4 +278,3 @@ Example of how to wait for enqueued tasks to be completed::
210278
:class:`collections.deque` is an alternative implementation of unbounded
211279
queues with fast atomic :meth:`~collections.deque.append` and
212280
:meth:`~collections.deque.popleft` operations that do not require locking.
213-

Lib/queue.py

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,26 @@
44
from collections import deque
55
from heapq import heappush, heappop
66
from time import monotonic as time
7+
try:
8+
from _queue import SimpleQueue
9+
except ImportError:
10+
SimpleQueue = None
711

8-
__all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue']
12+
__all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue', 'SimpleQueue']
913

10-
class Empty(Exception):
11-
'Exception raised by Queue.get(block=0)/get_nowait().'
12-
pass
14+
15+
try:
16+
from _queue import Empty
17+
except AttributeError:
18+
class Empty(Exception):
19+
'Exception raised by Queue.get(block=0)/get_nowait().'
20+
pass
1321

1422
class Full(Exception):
1523
'Exception raised by Queue.put(block=0)/put_nowait().'
1624
pass
1725

26+
1827
class Queue:
1928
'''Create a queue object with a given maximum size.
2029
@@ -241,3 +250,72 @@ def _put(self, item):
241250

242251
def _get(self):
243252
return self.queue.pop()
253+
254+
255+
class _PySimpleQueue:
256+
'''Simple, unbounded FIFO queue.
257+
258+
This pure Python implementation is not reentrant.
259+
'''
260+
# Note: while this pure Python version provides fairness
261+
# (by using a threading.Semaphore which is itself fair, being based
262+
# on threading.Condition), fairness is not part of the API contract.
263+
# This allows the C version to use a different implementation.
264+
265+
def __init__(self):
266+
self._queue = deque()
267+
self._count = threading.Semaphore(0)
268+
269+
def put(self, item, block=True, timeout=None):
270+
'''Put the item on the queue.
271+
272+
The optional 'block' and 'timeout' arguments are ignored, as this method
273+
never blocks. They are provided for compatibility with the Queue class.
274+
'''
275+
self._queue.append(item)
276+
self._count.release()
277+
278+
def get(self, block=True, timeout=None):
279+
'''Remove and return an item from the queue.
280+
281+
If optional args 'block' is true and 'timeout' is None (the default),
282+
block if necessary until an item is available. If 'timeout' is
283+
a non-negative number, it blocks at most 'timeout' seconds and raises
284+
the Empty exception if no item was available within that time.
285+
Otherwise ('block' is false), return an item if one is immediately
286+
available, else raise the Empty exception ('timeout' is ignored
287+
in that case).
288+
'''
289+
if timeout is not None and timeout < 0:
290+
raise ValueError("'timeout' must be a non-negative number")
291+
if not self._count.acquire(block, timeout):
292+
raise Empty
293+
return self._queue.popleft()
294+
295+
def put_nowait(self, item):
296+
'''Put an item into the queue without blocking.
297+
298+
This is exactly equivalent to `put(item)` and is only provided
299+
for compatibility with the Queue class.
300+
'''
301+
return self.put(item, block=False)
302+
303+
def get_nowait(self):
304+
'''Remove and return an item from the queue without blocking.
305+
306+
Only get an item if one is immediately available. Otherwise
307+
raise the Empty exception.
308+
'''
309+
return self.get(block=False)
310+
311+
def empty(self):
312+
'''Return True if the queue is empty, False otherwise (not reliable!).'''
313+
return len(self._queue) == 0
314+
315+
def qsize(self):
316+
'''Return the approximate size of the queue (not reliable!).'''
317+
return len(self._queue)
318+
319+
320+
if SimpleQueue is None:
321+
SimpleQueue = _PySimpleQueue

0 commit comments

Comments
 (0)