Skip to content
Snippets Groups Projects
Commit b774d24b authored by Pierre Neidhardt's avatar Pierre Neidhardt
Browse files

Version 0.1.

parents
No related branches found
No related tags found
No related merge requests found
The software and this document are licensed under permissive, BSD-like
terms, copied from the ISC license:
Copyright (c) 2009, Jean-Paul Guy Larocque
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
prepare_release:
$(MAKE) -C doc
clean:
$(MAKE) -C doc clean
(in-package #:jpl-queues)
(defclass bounded-fifo-queue (queue)
((buffer :type vector
:documentation "A ring buffer of elements, oldest first,
that have yet to be read.")
(size :type jpl-util:array-dimension :initform 0 :reader size
:documentation "The number of elements queued in BUFFER.")
(start :type jpl-util:array-index :initform 0
:documentation "The index into BUFFER of its oldest
element."))
(:documentation "A bounded FIFO queue. The queue capacity (a
positive integer less than ARRAY-DIMENSION-LIMIT) must be specified
with the :CAPACITY keyword parameter.
Space usage is constant after instantiation, in proportion to the
capacity. Conses little, if at all, for any operation."))
(defmethod initialize-instance :after ((queue bounded-fifo-queue)
&key (capacity nil capacity-p)
&allow-other-keys)
(unless capacity-p
(error "Must specify queue capacity with :CAPACITY keyword ~
parameter when instantiating a ~S."
(type-of queue)))
(check-type capacity jpl-util:array-dimension
"a queue capacity small enough to be an array dimension")
(when (zerop capacity)
(error "A 0-length queue would only block."))
(setf (slot-value queue 'buffer) (make-array capacity)))
(defmethod print-object ((queue bounded-fifo-queue) stream)
(print-unreadable-object (queue stream :type t :identity t)
(format stream "capacity ~D" (capacity queue))))
(defmethod capacity ((queue bounded-fifo-queue))
(with-slots (buffer) queue
(array-dimension buffer 0)))
(defmethod enqueue (object (queue bounded-fifo-queue))
(with-slots (buffer start size) queue
(setf (aref buffer (mod (+ start size) (capacity queue)))
object)
(incf size)))
(defmethod dequeue ((queue bounded-fifo-queue))
(with-slots (buffer start size) queue
(prog1 (aref buffer start)
;; Old elements can be big and otherwise dead. Don't hold onto
;; them, or else the GC will consider them to be alive.
(setf (aref buffer start) nil)
(setf start (mod (1+ start) (capacity queue)))
(decf size))))
(defmethod dequeue-object-if (predicate (queue bounded-fifo-queue)
&key (key #'identity))
(with-slots (buffer start size) queue
(flet ((physical-index (i)
(mod (+ start i) (capacity queue))))
(loop for input-pos from 0 below size
for output-pos = 0 then (- input-pos offset)
for element = (aref buffer (physical-index input-pos))
for delete? = (funcall predicate (funcall key element))
counting delete? into offset
unless (or delete? (= input-pos output-pos))
doing (setf (aref buffer (physical-index output-pos))
element)
finally (loop for del-pos from (- size offset) below size
doing (setf (aref buffer (physical-index del-pos)) nil))
finally (decf size offset)))))
(defun test-bounded-fifo-queue-dequeue-object-if (list-length)
(flet ((mod4? (x)
;; Pattern: X...X...X...
(zerop (mod x 4)))
(not-mod4? (x)
;; Pattern: .XXX.XXX.XXX
(plusp (mod x 4)))
(group3-1? (x)
;; Pattern: ...XXX...XXX
(oddp (floor x 3)))
(group3-2? (x)
;; Pattern: XXX...XXX...
(evenp (floor x 3)))
(always (x)
(declare (ignore x))
t)
(never (x)
(declare (ignore x))
nil))
(let ((objects (loop for i below list-length collecting i))
(queue-capacities (loop for queue-capacity from list-length upto (* 2 list-length)
collecting queue-capacity))
(tests (list #'evenp #'oddp #'mod4? #'not-mod4? #'group3-1?
#'group3-2? #'always #'never)))
(dolist (queue-capacity queue-capacities)
(dotimes (queue-start-val queue-capacity)
(dolist (test tests)
(let ((queue (make-instance 'bounded-fifo-queue :capacity queue-capacity)))
;; We'll test to ensure that everything ends up NIL, so
;; make sure each position is defined.
(fill (slot-value queue 'buffer) nil)
;; Manipulate the start pointer.
(dotimes (i queue-start-val)
(enqueue queue 'junk)
(dequeue queue))
;; Fill queue with test values.
(dolist (object objects)
(assert (not (full? queue)))
(enqueue queue object))
;; The moment of truth.
(dequeue-object-if test queue)
(assert (equal (remove-if test objects)
(loop until (empty? queue)
collecting (dequeue queue))))
;; Ensure old places were NILed out.
(assert (= (count-if #'null (slot-value queue 'buffer))
(- queue-capacity list-length))))))))))
To build the documentation on Debian, install "docbook-xsl" and "w3m".
For other systems, you'll need to install the equivalent packages or
get the stylesheets and w3m yourself, and then adjust the path of the
import in html.xsl.
all: doc.html doc.txt
clean:
rm -f doc.html doc.txt
doc.html: doc.xml
# When the stylesheets warn, they print a message but exit without error.
if [ "$$(xsltproc --output doc.html \
--stringparam section.autolabel 1 \
--stringparam toc.section.depth 4 \
html.xsl doc.xml 2>&1 \
| tee /dev/stderr)" ]; then \
rm -f doc.html; \
exit 1; \
fi
doc.txt: doc.html
w3m -dump doc.html > doc.txt
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>jpl-queues</title><meta name="generator" content="DocBook XSL Stylesheets V1.73.2"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="article" lang="en"><div class="titlepage"><div><div><h1 class="title"><a name="top"></a>jpl-queues</h1></div><div><div class="author"><h3 class="author"><span class="firstname">J.P.</span> <span class="surname">Larocque</span></h3></div></div></div><hr></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#sec-intro">1. Introduction</a></span></dt><dd><dl><dt><span class="section"><a href="#sec-obtaining">1.1. Obtaining jpl-queues</a></span></dt><dt><span class="section"><a href="#sec-copying">1.2. Copying</a></span></dt><dt><span class="section"><a href="#sec-contact">1.3. Contact Information</a></span></dt></dl></dd><dt><span class="section"><a href="#sec-examples">2. Examples</a></span></dt><dt><span class="section"><a href="#sec-reference">3. Reference</a></span></dt><dd><dl><dt><span class="section"><a href="#sec-classes">3.1. Queue Classes</a></span></dt><dd><dl><dt><span class="section"><a href="#sec-classes-queue">3.1.1. The <code class="type">QUEUE</code> Abstract Class</a></span></dt><dt><span class="section"><a href="#sec-classes-bounded_fifo_queue">3.1.2. The <code class="type">BOUNDED-FIFO-QUEUE</code> Class</a></span></dt><dt><span class="section"><a href="#sec-classes-unbounded_fifo_queue">3.1.3. The <code class="type">UNBOUNDED-FIFO-QUEUE</code> Class</a></span></dt><dt><span class="section"><a href="#sec-classes-lossy_bounded_fifo_queue">3.1.4. The <code class="type">LOSSY-BOUNDED-FIFO-QUEUE</code> Class</a></span></dt><dt><span class="section"><a href="#sec-classes-unbounded_random_queue">3.1.5. The <code class="type">UNBOUNDED-RANDOM-QUEUE</code> Class</a></span></dt><dt><span class="section"><a href="#sec-classes-synchronized_queue">3.1.6. The <code class="type">SYNCHRONIZED-QUEUE</code> Class</a></span></dt></dl></dd><dt><span class="section"><a href="#sec-api">3.2. The jpl-queues API</a></span></dt><dd><dl><dt><span class="section"><a href="#sec-api-empty">3.2.1. <code class="function">EMPTY?</code>: Ability to Dequeue
Elements</a></span></dt><dt><span class="section"><a href="#sec-api-full">3.2.2. <code class="function">FULL?</code>: Ability to Enqueue
Elements</a></span></dt><dt><span class="section"><a href="#sec-api-size">3.2.3. <code class="function">SIZE</code>: Enqueued Element
Count</a></span></dt><dt><span class="section"><a href="#sec-api-capacity">3.2.4. <code class="function">CAPACITY</code>: Enqueued Element
Limit</a></span></dt><dt><span class="section"><a href="#sec-api-enqueue">3.2.5. <code class="function">ENQUEUE</code>: Add an Element</a></span></dt><dt><span class="section"><a href="#sec-api-dequeue">3.2.6. <code class="function">DEQUEUE</code>: Remove an Element</a></span></dt><dt><span class="section"><a href="#sec-api-dequeue_object">3.2.7. <code class="function">DEQUEUE-OBJECT</code> and
<code class="function">DEQUEUE-OBJECT-IF</code>: Conditional Removal
of Elements</a></span></dt></dl></dd></dl></dd></dl></div><div class="section" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="sec-intro"></a>1. Introduction</h2></div></div></div><p>jpl-queues is a Common Lisp library implementing a few
different kinds of queues.</p><p>These kinds of queues are provided:</p><div class="itemizedlist"><ul type="disc"><li><a class="link" href="#sec-classes-bounded_fifo_queue" title="3.1.2. The BOUNDED-FIFO-QUEUE Class">Bounded</a>
and <a class="link" href="#sec-classes-unbounded_fifo_queue" title="3.1.3. The UNBOUNDED-FIFO-QUEUE Class">unbounded
FIFO queues</a>.</li><li><a class="link" href="#sec-classes-lossy_bounded_fifo_queue" title="3.1.4. The LOSSY-BOUNDED-FIFO-QUEUE Class">Lossy bounded FIFO queues</a> that drop elements when
full.</li><li><a class="link" href="#sec-classes-unbounded_random_queue" title="3.1.5. The UNBOUNDED-RANDOM-QUEUE Class">Unbounded
random-order queues</a> that use less memory than unbounded
FIFO queues.</li></ul></div><p>Additionally,
a <a class="link" href="#sec-classes-synchronized_queue" title="3.1.6. The SYNCHRONIZED-QUEUE Class">synchronization
wrapper</a> is provided to make any queue conforming to
the <a class="link" href="#sec-api" title="3.2. The jpl-queues API">jpl-queues API</a> thread-safe
for lightweight multithreading applications.
(See <a class="ulink" href="http://www.thoughtcrime.us/software/calispel/" target="_top">Calispel</a>
for a more sophisticated CL multithreaded message-passing
library with timeouts and alternation among several blockable
channels.)</p><p>jpl-queues is used
by <a class="ulink" href="http://www.thoughtcrime.us/software/calispel/" target="_top">Calispel</a>.</p><div class="section" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="sec-obtaining"></a>1.1. Obtaining jpl-queues</h3></div></div></div><p>The latest version of jpl-queues, with accompanying
documentation, can be found at:
<a class="ulink" href="http://www.thoughtcrime.us/software/jpl-queues/" target="_top">http://www.thoughtcrime.us/software/jpl-queues/</a></p><p>The most recent release is 0.1, released 2009-10-19. It
depends on:
<span class="simplelist"><a class="ulink" href="http://www.thoughtcrime.us/software/cl-jpl-util/" target="_top">cl-jpl-util</a>
0.2, <a class="ulink" href="http://common-lisp.net/project/bordeaux-threads/" target="_top">Bordeaux Threads</a></span>
</p><div class="itemizedlist"><ul type="disc"><li><a class="ulink" href="http://www.thoughtcrime.us/software/jpl-queues/jpl-queues-0.1.tar.gz" target="_top">jpl-queues-0.1.tar.gz</a>:
ASDF package</li><li><a class="ulink" href="http://www.thoughtcrime.us/software/jpl-queues/jpl-queues-0.1.tar.gz.sign" target="_top">jpl-queues-0.1.tar.gz.sign</a>:
OpenPGP detached signature</li></ul></div><p>
</p><p>I sign all my software with OpenPGP, key ID 0x80555CED7394F948,
fingerprint 2771 AF53 5D09 BDFB A8D0 BEF3 8055 5CED 7394 F948.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="sec-copying"></a>1.2. Copying</h3></div></div></div><p>The software and this document are licensed under
permissive, BSD-like terms, copied from the ISC
license:</p><div class="blockquote"><blockquote class="blockquote"><p>Copyright © 2009, Jean-Paul Guy Larocque</p><p>Permission to use, copy, modify, and/or distribute this
software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this
permission notice appear in all copies.</p><p>THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR
DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH
THE USE OR PERFORMANCE OF THIS SOFTWARE.</p></blockquote></div></div><div class="section" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="sec-contact"></a>1.3. Contact Information</h3></div></div></div><p>The author welcomes feedback, questions, help requests,
and bug reports via e-mail: J.P. Larocque &lt;jpl-software at
thoughtcrime.us&gt;</p></div></div><div class="section" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="sec-examples"></a>2. Examples</h2></div></div></div><p>Create a queue that can hold an unlimited number of elements
in FIFO order, which is not permitted to drop elements:</p><div class="informalexample"><pre class="programlisting">(defparameter *queue*
(make-instance 'jpl-queues:unbounded-fifo-queue))</pre></div><p>Enqueue some elements:</p><div class="informalexample"><pre class="programlisting">(loop for i below 10
until (jpl-queues:full? *queue*)
doing (jpl-queues:enqueue i *queue*))</pre></div><p>(Note that because our queue is unbounded, the
<a class="link" href="#sec-api-full" title="3.2.2. FULL?: Ability to Enqueue Elements"><code class="function">FULL?</code></a>
check is, strictly speaking, unnecessary. However, it's good
habit to check the queue first. Enqueuing to a full queue or
dequeuing from an empty queue may corrupt the queue's
state.)</p><p>Dequeue those elements:</p><div class="informalexample"><pre class="programlisting">(loop until (jpl-queues:empty? *queue*)
collecting (jpl-queues:dequeue *queue*))
=&gt; (0 1 2 3 4 5 6 7 8 9)</pre></div></div><div class="section" lang="en"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="sec-reference"></a>3. Reference</h2></div></div></div><div class="section" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="sec-classes"></a>3.1. Queue Classes</h3></div></div></div><p>Several classes are available, each offering different
queuing policies.</p><p>Queues can be classified in a few different
dimensions:</p><div class="variablelist"><dl><dt><span class="term">Element order</span></dt><dd>Typically FIFO (first-in, first-out), where the
order of elements coming out is the same as the order of
elements going in. Another possibility is random-order,
where the choice of an element upon dequeuing is random
among the available enqueued elements.</dd><dt><span class="term">Boundedness</span></dt><dd>Whether a queue has a fixed upper-bound to the
number of elements it can have enqueued. When a class of
queue is bounded, the specific capacity is chosen at
instantiation-time.</dd><dt><span class="term">Exactness</span></dt><dd>Whether a queue is required to keep each enqueued
element. A non-exact (or lossy) queue may drop elements
to make room for new ones.</dd></dl></div><p>Queues are created by
calling <a class="ulink" href="http://www.lispworks.com/documentation/HyperSpec/Body/f_mk_ins.htm#make-instance" target="_top"><code class="function">MAKE-INSTANCE</code></a>
with the name of one of the concrete queue classes. The queue
classes are described below.</p><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-classes-queue"></a>3.1.1. The <code class="type">QUEUE</code> Abstract Class</h4></div></div></div><p>A queue stores values which are enqueued, to be dequeued
at a later time.</p><p>The <code class="type">QUEUE</code> abstract class cannot be directly
instantiated and used by itself. Clients must instantiate a
subclass that provides specific queuing policies, or
subclass <code class="type">QUEUE</code> to implement their own queue
according to the jpl-queues API: see
<a class="xref" href="#sec-api" title="3.2. The jpl-queues API">Section 3.2, “The jpl-queues API”</a></p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-classes-bounded_fifo_queue"></a>3.1.2. The <code class="type">BOUNDED-FIFO-QUEUE</code> Class</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">MAKE-INSTANCE</code> <code class="literal">'BOUNDED-FIFO-QUEUE</code> &amp;key <code class="varname">CAPACITY</code>)
=&gt; <span class="emphasis"><em>(A <code class="type">BOUNDED-FIFO-QUEUE</code> instance.)</em></span></pre><p>A bounded FIFO queue. The queue capacity (a positive
integer less
than <a class="ulink" href="http://www.lispworks.com/documentation/HyperSpec/Body/v_ar_dim.htm#array-dimension-limit" target="_top"><code class="varname">ARRAY-DIMENSION-LIMIT</code></a>)
must be specified with the <code class="varname">:CAPACITY</code>
keyword parameter.</p><p>Space usage is constant after instantiation, in
proportion to the capacity. Conses little, if at all, for
any operation.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-classes-unbounded_fifo_queue"></a>3.1.3. The <code class="type">UNBOUNDED-FIFO-QUEUE</code> Class</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">MAKE-INSTANCE</code> <code class="literal">'UNBOUNDED-FIFO-QUEUE</code>)
=&gt; <span class="emphasis"><em>(An <code class="type">UNBOUNDED-FIFO-QUEUE</code> instance.)</em></span></pre><p>An unbounded FIFO queue.</p><p>Space usage is proportional to the number of queued
elements at any given point in time. Conses for each
enqueued element.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-classes-lossy_bounded_fifo_queue"></a>3.1.4. The <code class="type">LOSSY-BOUNDED-FIFO-QUEUE</code> Class</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">MAKE-INSTANCE</code> <code class="literal">'LOSSY-BOUNDED-FIFO-QUEUE</code> &amp;key <code class="varname">CAPACITY</code>)
=&gt; <span class="emphasis"><em>(A <code class="type">LOSSY-BOUNDED-FIFO-QUEUE</code> instance.)</em></span></pre><p>A bounded FIFO queue that throws away old entries
when <a class="link" href="#sec-api-enqueue" title="3.2.5. ENQUEUE: Add an Element"><code class="function">ENQUEUE</code></a>
is called and the queue is full. A queue capacity (a
positive integer less
than <a class="ulink" href="http://www.lispworks.com/documentation/HyperSpec/Body/v_ar_dim.htm#array-dimension-limit" target="_top"><code class="varname">ARRAY-DIMENSION-LIMIT</code></a>)
must be specified with the <code class="varname">:CAPACITY</code>
keyword parameter.</p><p>Space usage is constant after instantiation, in
proportion to the capacity. Conses little, if at all, for
any operation.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-classes-unbounded_random_queue"></a>3.1.5. The <code class="type">UNBOUNDED-RANDOM-QUEUE</code> Class</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">MAKE-INSTANCE</code> <code class="literal">'UNBOUNDED-RANDOM-QUEUE</code> &amp;key <code class="varname">CAPACITY</code>)
=&gt; <span class="emphasis"><em>(An <code class="type">UNBOUNDED-RANDOM-QUEUE</code> instance.)</em></span></pre><p>A virtually unbounded, random-order queue. (Strictly
speaking, the queue size must be less than
<a class="ulink" href="http://www.lispworks.com/documentation/HyperSpec/Body/v_ar_dim.htm#array-dimension-limit" target="_top"><code class="varname">ARRAY-DIMENSION-LIMIT</code></a>.)</p><p>Space usage is proportional to the peak number of queued
elements over the lifetime of the queue. An initial queue
capacity (a positive integer less
than <code class="varname">ARRAY-DIMENSION-LIMIT</code>) may
optionally be specified with
the <code class="varname">:CAPACITY</code> keyword parameter; the
capacity will be grown as required. Conses for an enqueued
element only when the current queue capacity is reached and
needs to be extended.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-classes-synchronized_queue"></a>3.1.6. The <code class="type">SYNCHRONIZED-QUEUE</code> Class</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">MAKE-INSTANCE</code> <code class="literal">'SYNCHRONIZED-QUEUE</code> &amp;key <code class="varname">QUEUE</code>)
=&gt; <span class="emphasis"><em>(A <code class="type">SYNCHRONIZED-QUEUE</code> instance.)</em></span></pre><p>Wraps another <code class="type">QUEUE</code> (given by
the <code class="varname">:QUEUE</code> keyword parameter),
synchronizing operations for safe multithreaded access.
After instantiating this queue, the
wrapped <code class="type">QUEUE</code> must not be directly used for the
duration of this queue.</p><p>When <a class="link" href="#sec-api-enqueue" title="3.2.5. ENQUEUE: Add an Element"><code class="function">ENQUEUE</code></a>
is called on this <code class="type">QUEUE</code> and the
wrapped <code class="type">QUEUE</code> is full, blocks until it is no
longer full.
When <a class="link" href="#sec-api-dequeue" title="3.2.6. DEQUEUE: Remove an Element"><code class="function">DEQUEUE</code></a>
is called on this <code class="type">QUEUE</code> and the
wrapped <code class="type">QUEUE</code> is empty, blocks until it is no
longer empty. This blocking also ensures that the internal
state of the wrapped <code class="type">QUEUE</code> won't be corrupted
by calling <code class="function">DEQUEUE</code> when empty
or <code class="function">ENQUEUE</code> when full.</p><p>Operations that should return no useful value will
return no values.</p><p>The time and space complexity for this queue and all its
operations are equal to those of the
wrapped <code class="type">QUEUE</code>, plus any locking overhead
imposed by the system, except
that <code class="function">ENQUEUE</code>
and <code class="function">DEQUEUE</code> may block
indefinitely.</p></div></div><div class="section" lang="en"><div class="titlepage"><div><div><h3 class="title"><a name="sec-api"></a>3.2. The jpl-queues API</h3></div></div></div><p>This section defines several generic functions that apply
to any instance of a concrete subclass
of <code class="type">QUEUE</code>.</p><p>Unless operating on an instance of a subclass that is
designed to support thread-safe access, no two operations may
occur at the same time in a multithreaded scenario, or else
either an incorrect or inconsistent answer may be returned, or
the internal state of the queue may be corrupted.</p><p>Implementers of <code class="type">QUEUE</code> subclasses are expected
to define methods for most of these functions. Some functions
have default methods which, for most <code class="type">QUEUE</code>
subclasses, infer correct answers from other functions. Refer
to <code class="filename">interface.lisp</code> to find which functions
have default methods and what their semantics are.</p><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-api-empty"></a>3.2.1. <code class="function">EMPTY?</code>: Ability to Dequeue
Elements</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">EMPTY?</code> <code class="varname">QUEUE</code>)
=&gt; <span class="emphasis"><em>boolean</em></span></pre><p><code class="function">EMPTY?</code> returns a boolean indicating
whether the given <code class="varname">QUEUE</code> cannot have any
more elements dequeued from it. When this function returns
false, it is safe to
call <a class="link" href="#sec-api-dequeue" title="3.2.6. DEQUEUE: Remove an Element"><code class="function">DEQUEUE</code></a>.</p><p>Most implementations will take constant time.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-api-full"></a>3.2.2. <code class="function">FULL?</code>: Ability to Enqueue
Elements</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">FULL?</code> <code class="varname">QUEUE</code>)
=&gt; <span class="emphasis"><em>boolean</em></span></pre><p><code class="function">FULL?</code> returns a boolean indicating
whether the given <code class="varname">QUEUE</code> cannot have any
more elements enqueued to it. When this function returns
false, it is safe to
call <a class="link" href="#sec-api-enqueue" title="3.2.5. ENQUEUE: Add an Element"><code class="function">ENQUEUE</code></a>.</p><p>This is subtly different than saying
its <a class="link" href="#sec-api-size" title="3.2.3. SIZE: Enqueued Element Count"><code class="function">SIZE</code></a>
is equal to
its <a class="link" href="#sec-api-capacity" title="3.2.4. CAPACITY: Enqueued Element Limit"><code class="function">CAPACITY</code></a>:
a lossy queue may have a capacity, and it may be "full" in
that sense, but it will still accept new elements to be
enqueued.</p><p>Most implementations will take constant time.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-api-size"></a>3.2.3. <code class="function">SIZE</code>: Enqueued Element
Count</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">SIZE</code> <code class="varname">QUEUE</code>)
=&gt; <span class="emphasis"><em>(A non-negative integer.)</em></span></pre><p>Returns the number of elements stored
in <code class="varname">QUEUE</code>.</p><p>Most implementations will take constant time.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-api-capacity"></a>3.2.4. <code class="function">CAPACITY</code>: Enqueued Element
Limit</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">CAPACITY</code> <code class="varname">QUEUE</code>)
=&gt; <span class="emphasis"><em>(a non-negative integer, or <code class="literal">NIL</code>)</em></span></pre><p>Returns the number of elements that can be stored at
once in the given <code class="varname">QUEUE</code>,
or <code class="literal">NIL</code> if there is no limit.</p><p>Most implementations will take constant time.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-api-enqueue"></a>3.2.5. <code class="function">ENQUEUE</code>: Add an Element</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">ENQUEUE</code> <code class="varname">OBJECT</code> <code class="varname">QUEUE</code>)
=&gt; <span class="emphasis"><em>(unspecified)</em></span></pre><p>Writes <code class="varname">OBJECT</code>
to <code class="varname">QUEUE</code>.</p><p>After this function
returns, <a class="link" href="#sec-api-empty" title="3.2.1. EMPTY?: Ability to Dequeue Elements"><code class="function">EMPTY?</code></a>
must return false.</p><p>It is the caller's responsibility to determine
whether <code class="varname">QUEUE</code> has room for another
element. The consequences are undefined if it
doesn't.</p><p>Most implementations will take constant time.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-api-dequeue"></a>3.2.6. <code class="function">DEQUEUE</code>: Remove an Element</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">DEQUEUE</code> <code class="varname">QUEUE</code>)
=&gt; <code class="varname">OBJECT</code></pre><p>Removes an element from <code class="varname">QUEUE</code>,
returning the element.</p><p>After this function
returns, <a class="link" href="#sec-api-full" title="3.2.2. FULL?: Ability to Enqueue Elements"><code class="function">FULL?</code></a>
must return false.</p><p>It is the caller's responsibility to determine
whether <code class="varname">QUEUE</code> has an element available
for reading. The consequences are undefined if it
doesn't.</p><p>Most implementations will take constant time.</p></div><div class="section" lang="en"><div class="titlepage"><div><div><h4 class="title"><a name="sec-api-dequeue_object"></a>3.2.7. <code class="function">DEQUEUE-OBJECT</code> and
<code class="function">DEQUEUE-OBJECT-IF</code>: Conditional Removal
of Elements</h4></div></div></div><p><b>Syntax. </b></p><pre class="synopsis">(<code class="function">DEQUEUE-OBJECT</code> <code class="varname">OBJECT</code> <code class="varname">QUEUE</code> &amp;key <code class="varname">TEST</code> <code class="varname">KEY</code>)
=&gt; <span class="emphasis"><em>(unspecified)</em></span></pre><pre class="synopsis">(<code class="function">DEQUEUE-OBJECT-IF</code> <code class="varname">PREDICATE</code> <code class="varname">QUEUE</code> &amp;key <code class="varname">KEY</code>)
=&gt; <span class="emphasis"><em>(unspecified)</em></span></pre><p>Prematurely dequeues elements
from <code class="varname">QUEUE</code>.</p><p><code class="function">DEQUEUE-OBJECT</code> dequeues each
instance
of <code class="varname">OBJECT</code>. <code class="varname">TEST</code> is a
function of two arguments that is used to
compare <code class="varname">OBJECT</code> with the result
of <code class="varname">KEY</code> applied to each enqueued element.
When <code class="varname">TEST</code> returns true, the element is
deleted. Zero or more elements may be matched and
removed.</p><p><code class="function">DEQUEUE-OBJECT-IF</code> dequeues each
element that <code class="varname">PREDICATE</code> returns true
for. <code class="varname">PREDICATE</code> is called with the result
of <code class="varname">KEY</code> applied to each enqueued element.
When <code class="varname">PREDICATE</code> returns true, the element
is deleted. Zero or more elements may be matched and
removed.</p><p>If anything was actually
removed, <a class="link" href="#sec-api-full" title="3.2.2. FULL?: Ability to Enqueue Elements"><code class="function">FULL?</code></a>
must return false.</p><p>Most implementations
of <code class="function">DELETE-OBJECT</code> will have the same
time complexity
as <code class="function">DELETE-OBJECT-IF</code>.</p><p>Most implementations
of <code class="function">DELETE-OBJECT-IF</code> will take time in
linear proportion to the number of enqueued elements.</p></div></div></div></div></body></html>
jpl-queues
J.P. Larocque
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Table of Contents
1. Introduction
1.1. Obtaining jpl-queues
1.2. Copying
1.3. Contact Information
2. Examples
3. Reference
3.1. Queue Classes
3.1.1. The QUEUE Abstract Class
3.1.2. The BOUNDED-FIFO-QUEUE Class
3.1.3. The UNBOUNDED-FIFO-QUEUE Class
3.1.4. The LOSSY-BOUNDED-FIFO-QUEUE Class
3.1.5. The UNBOUNDED-RANDOM-QUEUE Class
3.1.6. The SYNCHRONIZED-QUEUE Class
3.2. The jpl-queues API
3.2.1. EMPTY?: Ability to Dequeue Elements
3.2.2. FULL?: Ability to Enqueue Elements
3.2.3. SIZE: Enqueued Element Count
3.2.4. CAPACITY: Enqueued Element Limit
3.2.5. ENQUEUE: Add an Element
3.2.6. DEQUEUE: Remove an Element
3.2.7. DEQUEUE-OBJECT and DEQUEUE-OBJECT-IF: Conditional Removal of
Elements
1. Introduction
jpl-queues is a Common Lisp library implementing a few different kinds of
queues.
These kinds of queues are provided:
● Bounded and unbounded FIFO queues.
● Lossy bounded FIFO queues that drop elements when full.
● Unbounded random-order queues that use less memory than unbounded FIFO
queues.
Additionally, a synchronization wrapper is provided to make any queue
conforming to the jpl-queues API thread-safe for lightweight multithreading
applications. (See Calispel for a more sophisticated CL multithreaded
message-passing library with timeouts and alternation among several blockable
channels.)
jpl-queues is used by Calispel.
1.1. Obtaining jpl-queues
The latest version of jpl-queues, with accompanying documentation, can be found
at: http://www.thoughtcrime.us/software/jpl-queues/
The most recent release is 0.1, released 2009-10-19. It depends on: cl-jpl-util
0.2, Bordeaux Threads
● jpl-queues-0.1.tar.gz: ASDF package
● jpl-queues-0.1.tar.gz.sign: OpenPGP detached signature
I sign all my software with OpenPGP, key ID 0x80555CED7394F948, fingerprint
2771 AF53 5D09 BDFB A8D0 BEF3 8055 5CED 7394 F948.
1.2. Copying
The software and this document are licensed under permissive, BSD-like terms,
copied from the ISC license:
Copyright © 2009, Jean-Paul Guy Larocque
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1.3. Contact Information
The author welcomes feedback, questions, help requests, and bug reports via
e-mail: J.P. Larocque <jpl-software at thoughtcrime.us>
2. Examples
Create a queue that can hold an unlimited number of elements in FIFO order,
which is not permitted to drop elements:
(defparameter *queue*
(make-instance 'jpl-queues:unbounded-fifo-queue))
Enqueue some elements:
(loop for i below 10
until (jpl-queues:full? *queue*)
doing (jpl-queues:enqueue i *queue*))
(Note that because our queue is unbounded, the FULL? check is, strictly
speaking, unnecessary. However, it's good habit to check the queue first.
Enqueuing to a full queue or dequeuing from an empty queue may corrupt the
queue's state.)
Dequeue those elements:
(loop until (jpl-queues:empty? *queue*)
collecting (jpl-queues:dequeue *queue*))
=> (0 1 2 3 4 5 6 7 8 9)
3. Reference
3.1. Queue Classes
Several classes are available, each offering different queuing policies.
Queues can be classified in a few different dimensions:
Element order
Typically FIFO (first-in, first-out), where the order of elements coming
out is the same as the order of elements going in. Another possibility is
random-order, where the choice of an element upon dequeuing is random among
the available enqueued elements.
Boundedness
Whether a queue has a fixed upper-bound to the number of elements it can
have enqueued. When a class of queue is bounded, the specific capacity is
chosen at instantiation-time.
Exactness
Whether a queue is required to keep each enqueued element. A non-exact (or
lossy) queue may drop elements to make room for new ones.
Queues are created by calling MAKE-INSTANCE with the name of one of the
concrete queue classes. The queue classes are described below.
3.1.1. The QUEUE Abstract Class
A queue stores values which are enqueued, to be dequeued at a later time.
The QUEUE abstract class cannot be directly instantiated and used by itself.
Clients must instantiate a subclass that provides specific queuing policies, or
subclass QUEUE to implement their own queue according to the jpl-queues API:
see Section 3.2, “The jpl-queues API”
3.1.2. The BOUNDED-FIFO-QUEUE Class
Syntax.
(MAKE-INSTANCE 'BOUNDED-FIFO-QUEUE &key CAPACITY)
=> (A BOUNDED-FIFO-QUEUE instance.)
A bounded FIFO queue. The queue capacity (a positive integer less than
ARRAY-DIMENSION-LIMIT) must be specified with the :CAPACITY keyword parameter.
Space usage is constant after instantiation, in proportion to the capacity.
Conses little, if at all, for any operation.
3.1.3. The UNBOUNDED-FIFO-QUEUE Class
Syntax.
(MAKE-INSTANCE 'UNBOUNDED-FIFO-QUEUE)
=> (An UNBOUNDED-FIFO-QUEUE instance.)
An unbounded FIFO queue.
Space usage is proportional to the number of queued elements at any given point
in time. Conses for each enqueued element.
3.1.4. The LOSSY-BOUNDED-FIFO-QUEUE Class
Syntax.
(MAKE-INSTANCE 'LOSSY-BOUNDED-FIFO-QUEUE &key CAPACITY)
=> (A LOSSY-BOUNDED-FIFO-QUEUE instance.)
A bounded FIFO queue that throws away old entries when ENQUEUE is called and
the queue is full. A queue capacity (a positive integer less than
ARRAY-DIMENSION-LIMIT) must be specified with the :CAPACITY keyword parameter.
Space usage is constant after instantiation, in proportion to the capacity.
Conses little, if at all, for any operation.
3.1.5. The UNBOUNDED-RANDOM-QUEUE Class
Syntax.
(MAKE-INSTANCE 'UNBOUNDED-RANDOM-QUEUE &key CAPACITY)
=> (An UNBOUNDED-RANDOM-QUEUE instance.)
A virtually unbounded, random-order queue. (Strictly speaking, the queue size
must be less than ARRAY-DIMENSION-LIMIT.)
Space usage is proportional to the peak number of queued elements over the
lifetime of the queue. An initial queue capacity (a positive integer less than
ARRAY-DIMENSION-LIMIT) may optionally be specified with the :CAPACITY keyword
parameter; the capacity will be grown as required. Conses for an enqueued
element only when the current queue capacity is reached and needs to be
extended.
3.1.6. The SYNCHRONIZED-QUEUE Class
Syntax.
(MAKE-INSTANCE 'SYNCHRONIZED-QUEUE &key QUEUE)
=> (A SYNCHRONIZED-QUEUE instance.)
Wraps another QUEUE (given by the :QUEUE keyword parameter), synchronizing
operations for safe multithreaded access. After instantiating this queue, the
wrapped QUEUE must not be directly used for the duration of this queue.
When ENQUEUE is called on this QUEUE and the wrapped QUEUE is full, blocks
until it is no longer full. When DEQUEUE is called on this QUEUE and the
wrapped QUEUE is empty, blocks until it is no longer empty. This blocking also
ensures that the internal state of the wrapped QUEUE won't be corrupted by
calling DEQUEUE when empty or ENQUEUE when full.
Operations that should return no useful value will return no values.
The time and space complexity for this queue and all its operations are equal
to those of the wrapped QUEUE, plus any locking overhead imposed by the system,
except that ENQUEUE and DEQUEUE may block indefinitely.
3.2. The jpl-queues API
This section defines several generic functions that apply to any instance of a
concrete subclass of QUEUE.
Unless operating on an instance of a subclass that is designed to support
thread-safe access, no two operations may occur at the same time in a
multithreaded scenario, or else either an incorrect or inconsistent answer may
be returned, or the internal state of the queue may be corrupted.
Implementers of QUEUE subclasses are expected to define methods for most of
these functions. Some functions have default methods which, for most QUEUE
subclasses, infer correct answers from other functions. Refer to interface.lisp
to find which functions have default methods and what their semantics are.
3.2.1. EMPTY?: Ability to Dequeue Elements
Syntax.
(EMPTY? QUEUE)
=> boolean
EMPTY? returns a boolean indicating whether the given QUEUE cannot have any
more elements dequeued from it. When this function returns false, it is safe to
call DEQUEUE.
Most implementations will take constant time.
3.2.2. FULL?: Ability to Enqueue Elements
Syntax.
(FULL? QUEUE)
=> boolean
FULL? returns a boolean indicating whether the given QUEUE cannot have any more
elements enqueued to it. When this function returns false, it is safe to call
ENQUEUE.
This is subtly different than saying its SIZE is equal to its CAPACITY: a lossy
queue may have a capacity, and it may be "full" in that sense, but it will
still accept new elements to be enqueued.
Most implementations will take constant time.
3.2.3. SIZE: Enqueued Element Count
Syntax.
(SIZE QUEUE)
=> (A non-negative integer.)
Returns the number of elements stored in QUEUE.
Most implementations will take constant time.
3.2.4. CAPACITY: Enqueued Element Limit
Syntax.
(CAPACITY QUEUE)
=> (a non-negative integer, or NIL)
Returns the number of elements that can be stored at once in the given QUEUE,
or NIL if there is no limit.
Most implementations will take constant time.
3.2.5. ENQUEUE: Add an Element
Syntax.
(ENQUEUE OBJECT QUEUE)
=> (unspecified)
Writes OBJECT to QUEUE.
After this function returns, EMPTY? must return false.
It is the caller's responsibility to determine whether QUEUE has room for
another element. The consequences are undefined if it doesn't.
Most implementations will take constant time.
3.2.6. DEQUEUE: Remove an Element
Syntax.
(DEQUEUE QUEUE)
=> OBJECT
Removes an element from QUEUE, returning the element.
After this function returns, FULL? must return false.
It is the caller's responsibility to determine whether QUEUE has an element
available for reading. The consequences are undefined if it doesn't.
Most implementations will take constant time.
3.2.7. DEQUEUE-OBJECT and DEQUEUE-OBJECT-IF: Conditional Removal of Elements
Syntax.
(DEQUEUE-OBJECT OBJECT QUEUE &key TEST KEY)
=> (unspecified)
(DEQUEUE-OBJECT-IF PREDICATE QUEUE &key KEY)
=> (unspecified)
Prematurely dequeues elements from QUEUE.
DEQUEUE-OBJECT dequeues each instance of OBJECT. TEST is a function of two
arguments that is used to compare OBJECT with the result of KEY applied to each
enqueued element. When TEST returns true, the element is deleted. Zero or more
elements may be matched and removed.
DEQUEUE-OBJECT-IF dequeues each element that PREDICATE returns true for.
PREDICATE is called with the result of KEY applied to each enqueued element.
When PREDICATE returns true, the element is deleted. Zero or more elements may
be matched and removed.
If anything was actually removed, FULL? must return false.
Most implementations of DELETE-OBJECT will have the same time complexity as
DELETE-OBJECT-IF.
Most implementations of DELETE-OBJECT-IF will take time in linear proportion to
the number of enqueued elements.
<?xml version="1.0"?>
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
<article>
<artheader id="top">
<title>jpl-queues</title>
<author><firstname>J.P.</firstname> <surname>Larocque</surname></author>
</artheader>
<section id="sec-intro">
<title>Introduction</title>
<para>jpl-queues is a Common Lisp library implementing a few
different kinds of queues.</para>
<para>These kinds of queues are provided:</para>
<itemizedlist>
<listitem><link linkend="sec-classes-bounded_fifo_queue">Bounded</link>
and <link linkend="sec-classes-unbounded_fifo_queue">unbounded
FIFO queues</link>.</listitem>
<listitem><link linkend="sec-classes-lossy_bounded_fifo_queue">Lossy bounded FIFO queues</link> that drop elements when
full.</listitem>
<listitem><link linkend="sec-classes-unbounded_random_queue">Unbounded
random-order queues</link> that use less memory than unbounded
FIFO queues.</listitem>
</itemizedlist>
<para>Additionally,
a <link linkend="sec-classes-synchronized_queue">synchronization
wrapper</link> is provided to make any queue conforming to
the <link linkend="sec-api">jpl-queues API</link> thread-safe
for lightweight multithreading applications.
(See <ulink url="http://www.thoughtcrime.us/software/calispel/">Calispel</ulink>
for a more sophisticated CL multithreaded message-passing
library with timeouts and alternation among several blockable
channels.)</para>
<para>jpl-queues is used
by <ulink url="http://www.thoughtcrime.us/software/calispel/">Calispel</ulink>.</para>
<section id="sec-obtaining">
<title>Obtaining jpl-queues</title>
<para>The latest version of jpl-queues, with accompanying
documentation, can be found at:
<ulink url="http://www.thoughtcrime.us/software/jpl-queues/"/></para>
<para>The most recent release is 0.1, released 2009-10-19. It
depends on:
<simplelist type="inline">
<member><ulink url="http://www.thoughtcrime.us/software/cl-jpl-util/">cl-jpl-util</ulink>
0.2</member>
<member><ulink url="http://common-lisp.net/project/bordeaux-threads/">Bordeaux Threads</ulink></member>
</simplelist>
<itemizedlist>
<listitem><ulink url="http://www.thoughtcrime.us/software/jpl-queues/jpl-queues-0.1.tar.gz">jpl-queues-0.1.tar.gz</ulink>:
ASDF package</listitem>
<listitem><ulink url="http://www.thoughtcrime.us/software/jpl-queues/jpl-queues-0.1.tar.gz.sign">jpl-queues-0.1.tar.gz.sign</ulink>:
OpenPGP detached signature</listitem>
</itemizedlist>
</para>
<para>I sign all my software with OpenPGP, key ID 0x80555CED7394F948,
fingerprint 2771 AF53 5D09 BDFB A8D0 BEF3 8055 5CED 7394 F948.</para>
</section>
<section id="sec-copying">
<title>Copying</title>
<para>The software and this document are licensed under
permissive, BSD-like terms, copied from the ISC
license:</para>
<blockquote>
<para>Copyright &copy; 2009, Jean-Paul Guy Larocque</para>
<para>Permission to use, copy, modify, and/or distribute this
software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this
permission notice appear in all copies.</para>
<para>THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR
DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH
THE USE OR PERFORMANCE OF THIS SOFTWARE.</para>
</blockquote>
</section>
<section id="sec-contact">
<title>Contact Information</title>
<para>The author welcomes feedback, questions, help requests,
and bug reports via e-mail: J.P. Larocque &lt;jpl-software at
thoughtcrime.us&gt;</para>
</section>
</section>
<section id="sec-examples">
<title>Examples</title>
<para>Create a queue that can hold an unlimited number of elements
in FIFO order, which is not permitted to drop elements:</para>
<informalexample>
<programlisting>(defparameter *queue*
(make-instance 'jpl-queues:unbounded-fifo-queue))</programlisting>
</informalexample>
<para>Enqueue some elements:</para>
<informalexample>
<programlisting>(loop for i below 10
until (jpl-queues:full? *queue*)
doing (jpl-queues:enqueue i *queue*))</programlisting>
</informalexample>
<para>(Note that because our queue is unbounded, the
<link linkend="sec-api-full"><function>FULL?</function></link>
check is, strictly speaking, unnecessary. However, it's good
habit to check the queue first. Enqueuing to a full queue or
dequeuing from an empty queue may corrupt the queue's
state.)</para>
<para>Dequeue those elements:</para>
<informalexample>
<programlisting>(loop until (jpl-queues:empty? *queue*)
collecting (jpl-queues:dequeue *queue*))
=&gt; (0 1 2 3 4 5 6 7 8 9)</programlisting>
</informalexample>
</section>
<section id="sec-reference">
<title>Reference</title>
<section id="sec-classes">
<title>Queue Classes</title>
<para>Several classes are available, each offering different
queuing policies.</para>
<para>Queues can be classified in a few different
dimensions:</para>
<variablelist>
<varlistentry>
<term>Element order</term>
<listitem>Typically FIFO (first-in, first-out), where the
order of elements coming out is the same as the order of
elements going in. Another possibility is random-order,
where the choice of an element upon dequeuing is random
among the available enqueued elements.</listitem>
</varlistentry>
<varlistentry>
<term>Boundedness</term>
<listitem>Whether a queue has a fixed upper-bound to the
number of elements it can have enqueued. When a class of
queue is bounded, the specific capacity is chosen at
instantiation-time.</listitem>
</varlistentry>
<varlistentry>
<term>Exactness</term>
<listitem>Whether a queue is required to keep each enqueued
element. A non-exact (or lossy) queue may drop elements
to make room for new ones.</listitem>
</varlistentry>
</variablelist>
<para>Queues are created by
calling <ulink url="http://www.lispworks.com/documentation/HyperSpec/Body/f_mk_ins.htm#make-instance"><function>MAKE-INSTANCE</function></ulink>
with the name of one of the concrete queue classes. The queue
classes are described below.</para>
<section id="sec-classes-queue">
<title>The <type>QUEUE</type> Abstract Class</title>
<para>A queue stores values which are enqueued, to be dequeued
at a later time.</para>
<para>The <type>QUEUE</type> abstract class cannot be directly
instantiated and used by itself. Clients must instantiate a
subclass that provides specific queuing policies, or
subclass <type>QUEUE</type> to implement their own queue
according to the jpl-queues API: see
<xref linkend="sec-api"/></para>
</section>
<section id="sec-classes-bounded_fifo_queue">
<title>The <type>BOUNDED-FIFO-QUEUE</type> Class</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>MAKE-INSTANCE</function> <literal>'BOUNDED-FIFO-QUEUE</literal> &amp;key <varname>CAPACITY</varname>)
=&gt; <emphasis>(A <type>BOUNDED-FIFO-QUEUE</type> instance.)</emphasis></synopsis>
</formalpara>
<para>A bounded FIFO queue. The queue capacity (a positive
integer less
than <ulink url="http://www.lispworks.com/documentation/HyperSpec/Body/v_ar_dim.htm#array-dimension-limit"><varname>ARRAY-DIMENSION-LIMIT</varname></ulink>)
must be specified with the <varname>:CAPACITY</varname>
keyword parameter.</para>
<para>Space usage is constant after instantiation, in
proportion to the capacity. Conses little, if at all, for
any operation.</para>
</section>
<section id="sec-classes-unbounded_fifo_queue">
<title>The <type>UNBOUNDED-FIFO-QUEUE</type> Class</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>MAKE-INSTANCE</function> <literal>'UNBOUNDED-FIFO-QUEUE</literal>)
=&gt; <emphasis>(An <type>UNBOUNDED-FIFO-QUEUE</type> instance.)</emphasis></synopsis>
</formalpara>
<para>An unbounded FIFO queue.</para>
<para>Space usage is proportional to the number of queued
elements at any given point in time. Conses for each
enqueued element.</para>
</section>
<section id="sec-classes-lossy_bounded_fifo_queue">
<title>The <type>LOSSY-BOUNDED-FIFO-QUEUE</type> Class</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>MAKE-INSTANCE</function> <literal>'LOSSY-BOUNDED-FIFO-QUEUE</literal> &amp;key <varname>CAPACITY</varname>)
=&gt; <emphasis>(A <type>LOSSY-BOUNDED-FIFO-QUEUE</type> instance.)</emphasis></synopsis>
</formalpara>
<para>A bounded FIFO queue that throws away old entries
when <link linkend="sec-api-enqueue"><function>ENQUEUE</function></link>
is called and the queue is full. A queue capacity (a
positive integer less
than <ulink url="http://www.lispworks.com/documentation/HyperSpec/Body/v_ar_dim.htm#array-dimension-limit"><varname>ARRAY-DIMENSION-LIMIT</varname></ulink>)
must be specified with the <varname>:CAPACITY</varname>
keyword parameter.</para>
<para>Space usage is constant after instantiation, in
proportion to the capacity. Conses little, if at all, for
any operation.</para>
</section>
<section id="sec-classes-unbounded_random_queue">
<title>The <type>UNBOUNDED-RANDOM-QUEUE</type> Class</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>MAKE-INSTANCE</function> <literal>'UNBOUNDED-RANDOM-QUEUE</literal> &amp;key <varname>CAPACITY</varname>)
=&gt; <emphasis>(An <type>UNBOUNDED-RANDOM-QUEUE</type> instance.)</emphasis></synopsis>
</formalpara>
<para>A virtually unbounded, random-order queue. (Strictly
speaking, the queue size must be less than
<ulink url="http://www.lispworks.com/documentation/HyperSpec/Body/v_ar_dim.htm#array-dimension-limit"><varname>ARRAY-DIMENSION-LIMIT</varname></ulink>.)</para>
<para>Space usage is proportional to the peak number of queued
elements over the lifetime of the queue. An initial queue
capacity (a positive integer less
than <varname>ARRAY-DIMENSION-LIMIT</varname>) may
optionally be specified with
the <varname>:CAPACITY</varname> keyword parameter; the
capacity will be grown as required. Conses for an enqueued
element only when the current queue capacity is reached and
needs to be extended.</para>
</section>
<section id="sec-classes-synchronized_queue">
<title>The <type>SYNCHRONIZED-QUEUE</type> Class</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>MAKE-INSTANCE</function> <literal>'SYNCHRONIZED-QUEUE</literal> &amp;key <varname>QUEUE</varname>)
=&gt; <emphasis>(A <type>SYNCHRONIZED-QUEUE</type> instance.)</emphasis></synopsis>
</formalpara>
<para>Wraps another <type>QUEUE</type> (given by
the <varname>:QUEUE</varname> keyword parameter),
synchronizing operations for safe multithreaded access.
After instantiating this queue, the
wrapped <type>QUEUE</type> must not be directly used for the
duration of this queue.</para>
<para>When <link linkend="sec-api-enqueue"><function>ENQUEUE</function></link>
is called on this <type>QUEUE</type> and the
wrapped <type>QUEUE</type> is full, blocks until it is no
longer full.
When <link linkend="sec-api-dequeue"><function>DEQUEUE</function></link>
is called on this <type>QUEUE</type> and the
wrapped <type>QUEUE</type> is empty, blocks until it is no
longer empty. This blocking also ensures that the internal
state of the wrapped <type>QUEUE</type> won't be corrupted
by calling <function>DEQUEUE</function> when empty
or <function>ENQUEUE</function> when full.</para>
<para>Operations that should return no useful value will
return no values.</para>
<para>The time and space complexity for this queue and all its
operations are equal to those of the
wrapped <type>QUEUE</type>, plus any locking overhead
imposed by the system, except
that <function>ENQUEUE</function>
and <function>DEQUEUE</function> may block
indefinitely.</para>
</section>
</section>
<section id="sec-api">
<title>The jpl-queues API</title>
<para>This section defines several generic functions that apply
to any instance of a concrete subclass
of <type>QUEUE</type>.</para>
<para>Unless operating on an instance of a subclass that is
designed to support thread-safe access, no two operations may
occur at the same time in a multithreaded scenario, or else
either an incorrect or inconsistent answer may be returned, or
the internal state of the queue may be corrupted.</para>
<para>Implementers of <type>QUEUE</type> subclasses are expected
to define methods for most of these functions. Some functions
have default methods which, for most <type>QUEUE</type>
subclasses, infer correct answers from other functions. Refer
to <filename>interface.lisp</filename> to find which functions
have default methods and what their semantics are.</para>
<!-- FIXME: generate these following sections automatically from
docstrings. -->
<section id="sec-api-empty">
<title><function>EMPTY?</function>: Ability to Dequeue
Elements</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>EMPTY?</function> <varname>QUEUE</varname>)
=&gt; <emphasis>boolean</emphasis></synopsis>
</formalpara>
<para><function>EMPTY?</function> returns a boolean indicating
whether the given <varname>QUEUE</varname> cannot have any
more elements dequeued from it. When this function returns
false, it is safe to
call <link linkend="sec-api-dequeue"><function>DEQUEUE</function></link>.</para>
<para>Most implementations will take constant time.</para>
</section>
<section id="sec-api-full">
<title><function>FULL?</function>: Ability to Enqueue
Elements</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>FULL?</function> <varname>QUEUE</varname>)
=&gt; <emphasis>boolean</emphasis></synopsis>
</formalpara>
<para><function>FULL?</function> returns a boolean indicating
whether the given <varname>QUEUE</varname> cannot have any
more elements enqueued to it. When this function returns
false, it is safe to
call <link linkend="sec-api-enqueue"><function>ENQUEUE</function></link>.</para>
<para>This is subtly different than saying
its <link linkend="sec-api-size"><function>SIZE</function></link>
is equal to
its <link linkend="sec-api-capacity"><function>CAPACITY</function></link>:
a lossy queue may have a capacity, and it may be "full" in
that sense, but it will still accept new elements to be
enqueued.</para>
<para>Most implementations will take constant time.</para>
</section>
<section id="sec-api-size">
<title><function>SIZE</function>: Enqueued Element
Count</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>SIZE</function> <varname>QUEUE</varname>)
=&gt; <emphasis>(A non-negative integer.)</emphasis></synopsis>
</formalpara>
<para>Returns the number of elements stored
in <varname>QUEUE</varname>.</para>
<para>Most implementations will take constant time.</para>
</section>
<section id="sec-api-capacity">
<title><function>CAPACITY</function>: Enqueued Element
Limit</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>CAPACITY</function> <varname>QUEUE</varname>)
=&gt; <emphasis>(a non-negative integer, or <literal>NIL</literal>)</emphasis></synopsis>
</formalpara>
<para>Returns the number of elements that can be stored at
once in the given <varname>QUEUE</varname>,
or <literal>NIL</literal> if there is no limit.</para>
<para>Most implementations will take constant time.</para>
</section>
<section id="sec-api-enqueue">
<title><function>ENQUEUE</function>: Add an Element</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>ENQUEUE</function> <varname>OBJECT</varname> <varname>QUEUE</varname>)
=&gt; <emphasis>(unspecified)</emphasis></synopsis>
</formalpara>
<para>Writes <varname>OBJECT</varname>
to <varname>QUEUE</varname>.</para>
<para>After this function
returns, <link linkend="sec-api-empty"><function>EMPTY?</function></link>
must return false.</para>
<para>It is the caller's responsibility to determine
whether <varname>QUEUE</varname> has room for another
element. The consequences are undefined if it
doesn't.</para>
<para>Most implementations will take constant time.</para>
</section>
<section id="sec-api-dequeue">
<title><function>DEQUEUE</function>: Remove an Element</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>DEQUEUE</function> <varname>QUEUE</varname>)
=&gt; <varname>OBJECT</varname></synopsis>
</formalpara>
<para>Removes an element from <varname>QUEUE</varname>,
returning the element.</para>
<para>After this function
returns, <link linkend="sec-api-full"><function>FULL?</function></link>
must return false.</para>
<para>It is the caller's responsibility to determine
whether <varname>QUEUE</varname> has an element available
for reading. The consequences are undefined if it
doesn't.</para>
<para>Most implementations will take constant time.</para>
</section>
<section id="sec-api-dequeue_object">
<title><function>DEQUEUE-OBJECT</function> and
<function>DEQUEUE-OBJECT-IF</function>: Conditional Removal
of Elements</title>
<formalpara>
<title>Syntax</title>
<synopsis>(<function>DEQUEUE-OBJECT</function> <varname>OBJECT</varname> <varname>QUEUE</varname> &amp;key <varname>TEST</varname> <varname>KEY</varname>)
=&gt; <emphasis>(unspecified)</emphasis></synopsis>
<synopsis>(<function>DEQUEUE-OBJECT-IF</function> <varname>PREDICATE</varname> <varname>QUEUE</varname> &amp;key <varname>KEY</varname>)
=&gt; <emphasis>(unspecified)</emphasis></synopsis>
</formalpara>
<para>Prematurely dequeues elements
from <varname>QUEUE</varname>.</para>
<para><function>DEQUEUE-OBJECT</function> dequeues each
instance
of <varname>OBJECT</varname>. <varname>TEST</varname> is a
function of two arguments that is used to
compare <varname>OBJECT</varname> with the result
of <varname>KEY</varname> applied to each enqueued element.
When <varname>TEST</varname> returns true, the element is
deleted. Zero or more elements may be matched and
removed.</para>
<para><function>DEQUEUE-OBJECT-IF</function> dequeues each
element that <varname>PREDICATE</varname> returns true
for. <varname>PREDICATE</varname> is called with the result
of <varname>KEY</varname> applied to each enqueued element.
When <varname>PREDICATE</varname> returns true, the element
is deleted. Zero or more elements may be matched and
removed.</para>
<para>If anything was actually
removed, <link linkend="sec-api-full"><function>FULL?</function></link>
must return false.</para>
<para>Most implementations
of <function>DELETE-OBJECT</function> will have the same
time complexity
as <function>DELETE-OBJECT-IF</function>.</para>
<para>Most implementations
of <function>DELETE-OBJECT-IF</function> will take time in
linear proportion to the number of enqueued elements.</para>
</section>
</section>
</section>
</article>
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ng="http://docbook.org/docbook-ng"
xmlns:db="http://docbook.org/ns/docbook"
xmlns:exsl="http://exslt.org/common"
xmlns:exslt="http://exslt.org/common"
exclude-result-prefixes="db ng exsl exslt"
version='1.0'>
<xsl:import href="file:///usr/share/xml/docbook/stylesheet/nwalsh/html/docbook.xsl"/>
<!-- Unfortunately, docbook-xsl emits sloppy HTML, so we can't use
Strict. -->
<xsl:output method="html"
encoding="UTF-8"
indent="no"
doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN"
doctype-system="http://www.w3.org/TR/html4/loose.dtd"/>
<xsl:template match="replaceable">
<xsl:text>&lt;</xsl:text>
<xsl:call-template name="inline.italicseq"/>
<xsl:text>&gt;</xsl:text>
</xsl:template>
<xsl:template match="type">
<xsl:call-template name="inline.monoseq"/>
</xsl:template>
</xsl:stylesheet>
(in-package #:jpl-queues)
(defclass queue ()
()
(:documentation "Abstract class for queues.
Unless noted otherwise, subclasses do no synchronization. It is the
client's responsibility to ensure no two operations occur
simultaneously.
The usual time complexity for each operation is documented in the
generic function for the operation. Per-method exceptions should be
documented."))
;;; Generic functions for instances.
(defgeneric empty? (queue)
(:documentation "Returns a boolean indicating whether the given
QUEUE cannot have any more elements dequeued from it. When this
function returns false, it is safe to call DEQUEUE.
Most implementations will take constant time.")
(:method ((queue queue))
(zerop (size queue))))
(defgeneric full? (queue)
(:documentation "Returns a boolean indicating whether the given
QUEUE cannot have any more elements enqueued to it. When this
function returns false, it is safe to call ENQUEUE.
This is subtly different than saying its SIZE is equal to its
CAPACITY: a lossy queue may have a capacity, and it may be \"full\" in
that sense, but it will still accept new elements to be enqueued.
Most implementations will take constant time.")
(:method ((queue queue))
(let ((capacity (capacity queue)))
(and (not (null capacity))
(= capacity (size queue))))))
(defgeneric size (queue)
(:documentation "Returns the number of elements stored in QUEUE.
Most implementations will take constant time."))
(defgeneric capacity (queue)
(:documentation "Returns the number of elements that can be stored
at once in the given QUEUE, or NIL if there is no limit.
Most implementations will take constant time."))
(defgeneric enqueue (object queue)
(:documentation "Writes OBJECT to QUEUE.
After this function returns, EMPTY? must return false.
It is the caller's responsibility to determine whether QUEUE has room
for another element. The consequences are undefined if it doesn't.
Most implementations will take constant time."))
(defgeneric dequeue (queue)
(:documentation "Removes an element from QUEUE, returning the
element.
After this function returns, FULL? must return false.
It is the caller's responsibility to determine whether QUEUE has an
element available for reading. The consequences are undefined if it
doesn't.
Most implementations will take constant time."))
(defgeneric dequeue-object (object queue &key test key)
(:documentation "Prematurely dequeues OBJECT from QUEUE.
TEST is a function of two arguments that is used to compare OBJECT
with the result of KEY applied to each enqueued element. When TEST
returns true, the element is deleted. Zero or more elements may be
matched and removed.
If anything was actually removed, FULL? must return false.
Most implementations will have the same time complexity as
DELETE-OBJECT-IF.")
(:method (object (queue queue) &key (test #'eql) (key #'identity))
(dequeue-object-if (lambda (other-object)
(funcall test object other-object))
queue :key key)))
(defgeneric dequeue-object-if (predicate queue &key key)
(:documentation "Prematurely dequeues elements from QUEUE that
PREDICATE returns true for.
PREDICATE is called with the result of KEY applied to each enqueued
element. When PREDICATE returns true, the element is deleted. Zero
or more elements may be matched and removed.
If anything was actually removed, FULL? must return false.
Most implementations will take time in linear proportion to the number
of enqueued elements."))
;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp -*-
(asdf:defsystem "jpl-queues"
:version "0.1"
:maintainer "J.P. Larocque"
:author "J.P. Larocque"
:licence "ISC-style permissive"
:description "A few different kinds of queues, with optional
multithreading synchronization."
:components ((:file "interface"
:depends-on ("package"))
(:file "bounded-fifo"
:depends-on ("interface"
"package"))
(:file "lossy-bounded-fifo"
:depends-on ("bounded-fifo"
"interface"
"package"))
(:file "unbounded-fifo"
:depends-on ("interface"
"package"))
(:file "unbounded-random"
:depends-on ("interface"
"package"))
(:file "synchronized"
:depends-on ("interface"
"package"))
(:file "package"))
:depends-on ("bordeaux-threads" (:version "jpl-util" "0.2")))
(in-package #:jpl-queues)
;;; This could be made to be a mixin class, for use with other bounded
;;; queues.
(defclass lossy-bounded-fifo-queue (bounded-fifo-queue)
()
(:documentation "A bounded FIFO queue that throws away old entries
when ENQUEUE is called and the queue is full. A queue capacity (a
positive integer less than ARRAY-DIMENSION-LIMIT) must be specified
with the :CAPACITY keyword parameter.
Space usage is constant after instantiation, in proportion to the
capacity. Conses little, if at all, for any operation."))
(defmethod full? ((queue lossy-bounded-fifo-queue))
nil)
(defmethod enqueue :before (object (queue lossy-bounded-fifo-queue))
(when (= (size queue) (capacity queue))
(dequeue queue)))
(in-package #:common-lisp-user)
(defpackage #:jpl-queues
(:export #:queue
#:bounded-fifo-queue
#:lossy-bounded-fifo-queue
#:unbounded-fifo-queue
#:unbounded-random-queue
#:synchronized-queue
#:empty?
#:full?
#:size
#:capacity
#:enqueue
#:dequeue
#:dequeue-object
#:dequeue-object-if)
(:use #:common-lisp))
(in-package #:jpl-queues)
(defclass synchronized-queue (queue)
((queue :type queue :initarg :queue
:initform (error "Must specify :QUEUE.")
:documentation "The wrapped QUEUE for which operations are
synchronized.")
(lock :initform (bt:make-lock)
:documentation "The LOCK which must be held during each
operation.")
(enqueued :initform (bt:make-condition-variable)
:documentation "Condition variable that is notified each
time an element has been enqueued.")
(dequeued :initform (bt:make-condition-variable)
:documentation "Condition variable that is notified each
time an element has been dequeued."))
(:documentation "Wraps another QUEUE (given by the :QUEUE keyword
parameter), synchronizing operations for safe multithreaded access.
After instantiating this queue, the wrapped QUEUE must not be directly
used for the duration of this queue.
When ENQUEUE is called on this QUEUE and the wrapped QUEUE is full,
blocks until it is no longer full. When DEQUEUE is called on this
QUEUE and the wrapped QUEUE is empty, blocks until it is no longer
empty. This blocking also ensures that the internal state of the
wrapped QUEUE won't become corrupt by calling DEQUEUE when empty or
ENQUEUE when full.
Operations that should return no useful value will return no values.
The time and space complexity for this queue and all its operations
are equal to those of the wrapped QUEUE, plus any locking overhead
imposed by the system, except that ENQUEUE and DEQUEUE may block
indefinitely."))
(defmethod print-object ((queue synchronized-queue) stream)
(print-unreadable-object (queue stream :type t :identity t)
(format stream "for ~S" (slot-value queue 'queue))))
(defmethod empty? ((queue synchronized-queue))
(with-slots ((wrapped-queue queue) lock) queue
(bt:with-lock-held (lock)
(empty? wrapped-queue))))
(defmethod full? ((queue synchronized-queue))
(with-slots ((wrapped-queue queue) lock) queue
(bt:with-lock-held (lock)
(full? wrapped-queue))))
(defmethod size ((queue synchronized-queue))
(with-slots ((wrapped-queue queue) lock) queue
(bt:with-lock-held (lock)
(size wrapped-queue))))
(defmethod capacity ((queue synchronized-queue))
(with-slots ((wrapped-queue queue) lock) queue
(bt:with-lock-held (lock)
(capacity wrapped-queue))))
(defmethod enqueue (object (queue synchronized-queue))
(with-slots ((wrapped-queue queue) lock enqueued dequeued) queue
(bt:with-lock-held (lock)
(loop while (full? wrapped-queue)
doing (bt:condition-wait dequeued lock))
(enqueue object wrapped-queue)
(bt:condition-notify enqueued)))
(values))
(defmethod dequeue ((queue synchronized-queue))
(with-slots ((wrapped-queue queue) lock enqueued dequeued) queue
(bt:with-lock-held (lock)
(loop while (empty? wrapped-queue)
doing (bt:condition-wait enqueued lock))
(prog1 (dequeue wrapped-queue)
(bt:condition-notify dequeued)))))
(defmethod dequeue-object (object (queue synchronized-queue)
&key (test #'eql) (key #'identity))
(with-slots ((wrapped-queue queue) lock dequeued) queue
(bt:with-lock-held (lock)
(let ((old-size (size wrapped-queue)))
(dequeue-object object wrapped-queue :test test :key key)
;; Notify once for each element that was effectively dequeued.
;;
;; FIXME: Try modifying BORDEAUX-THREADS to take a
;; NOTIFY-COUNT parameter that takes care of this for us,
;; which on most threading implementations would probably be
;; in a smarter way than this.
(loop repeat (- old-size (size wrapped-queue))
doing (bt:condition-notify dequeued)))))
(values))
(defmethod dequeue-object-if (predicate (queue synchronized-queue)
&key (key #'identity))
(with-slots ((wrapped-queue queue) lock dequeued) queue
(bt:with-lock-held (lock)
(let ((old-size (size wrapped-queue)))
(dequeue-object-if predicate wrapped-queue :key key)
;; Notify once for each element that was effectively dequeued.
;;
;; FIXME: Try modifying BORDEAUX-THREADS to take a
;; NOTIFY-COUNT parameter that takes care of this for us,
;; which on most threading implementations would probably be
;; in a smarter way than this.
(loop repeat (- old-size (size wrapped-queue))
doing (bt:condition-notify dequeued)))))
(values))
(in-package #:jpl-queues)
;;; Technique copied from R. Scott McIntire's queue.lisp (RSM.QUEUE),
;;; but it probably goes back way further than that.
(defclass unbounded-fifo-queue (queue)
((buffer :type list :initform '() :reader buffer
:documentation "A list of elements, oldest first, that have
yet to be read.
The cells of the list do not share structure with anything outside the
queue.")
(last-cell :type (or cons null) :initform nil
:documentation "The last cons cell of BUFFER, or NIL if
BUFFER is empty.")
(size :type (integer 0) :initform 0 :reader size
:documentation "The number of elements in BUFFER."))
(:documentation "An unbounded FIFO queue.
Space usage is proportional to the number of queued elements at any
given point in time. Conses for each enqueued element."))
(defmethod capacity ((queue unbounded-fifo-queue))
nil)
(defmethod enqueue (object (queue unbounded-fifo-queue))
(with-slots (buffer last-cell size) queue
(setf last-cell
(if (null last-cell)
(setf buffer (list object))
(setf (rest last-cell) (list object))))
(incf size)))
(defmethod dequeue ((queue unbounded-fifo-queue))
(with-slots (buffer last-cell size) queue
(prog1 (pop buffer)
(decf size)
(when (endp buffer)
(setf last-cell nil)))))
(defmethod dequeue-object-if (predicate (queue unbounded-fifo-queue)
&key (key #'identity))
(with-slots (buffer last-cell size) queue
(setf buffer (delete-if predicate buffer :key key)
last-cell (last buffer)
size (length buffer))))
(in-package #:jpl-queues)
(defclass unbounded-random-queue (queue)
((buffer :type vector
:documentation "A buffer of elements, in unspecified order,
that have yet to be read."))
(:documentation "A virtually unbounded, random-order
queue. (Strictly speaking, the queue size is limited by
ARRAY-DIMENSION-LIMIT.)
Space usage is proportional to the peak number of queued elements over
the lifetime of the queue. An initial queue capacity (a positive
integer less than ARRAY-DIMENSION-LIMIT) may optionally be specified
with the :CAPACITY keyword parameter; the capacity will be grown as
required. Conses for an enqueued element only when the current queue
capacity is reached and needs to be extended."))
(defmethod initialize-instance :after ((queue unbounded-random-queue)
&key (capacity 0 capacity-p)
&allow-other-keys)
(when capacity-p
(check-type capacity jpl-util:array-dimension
"a queue capacity small enough to be an array dimension"))
(setf (slot-value queue 'buffer)
(make-array capacity :fill-pointer 0 :adjustable t)))
(defmethod size ((queue unbounded-random-queue))
(with-slots (buffer) queue
(fill-pointer buffer)))
(defmethod capacity ((queue unbounded-random-queue))
(1- array-dimension-limit))
(defmethod enqueue (object (queue unbounded-random-queue))
(with-slots (buffer) queue
(vector-push-extend object buffer)))
(defmethod dequeue ((queue unbounded-random-queue))
(with-slots (buffer) queue
(let ((index (random (fill-pointer buffer))))
(prog1 (aref buffer index)
(jpl-util:vector-delete buffer index)))))
(defmethod dequeue-object-if (predicate (queue unbounded-random-queue)
&key (key #'identity))
(with-slots (buffer) queue
(loop with i = 0
while (< i (fill-pointer buffer))
doing (cond ((funcall predicate (funcall key (aref buffer i)))
(jpl-util:vector-delete buffer i))
(t (incf i))))))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment