1 ;;;; Written by James M. Lawrence for SBCL.
2 ;;;; API and docstrings by Nikodemus Siivola.
4 ;;;; This software is part of the SBCL system. See the README file for
7 ;;;; This software is derived from the CMU CL system, which was written at
8 ;;;; Carnegie Mellon University and released into the public domain. The
9 ;;;; software is in the public domain and is provided with absolutely no
10 ;;;; warranty. See the COPYING and CREDITS files for more information.
12 ;;; Singly-linked queue with compare-and-swap operations.
14 ;;; The following invariants hold except during updates:
16 ;;; (car (queue-head queue)) == +dummy+
18 ;;; (cdr (queue-tail queue)) == nil
20 ;;; If the queue is empty, (queue-head queue) == (queue-tail queue).
22 ;;; If the queue is non-empty, (cadr (queue-head queue)) is the next
23 ;;; value to be dequeued and (car (queue-tail queue)) is the most
24 ;;; recently enqueued value.
26 ;;; The CDR of a discarded node is set to +DEAD-END+. This flag must
27 ;;; be checked at each traversal.
29 (in-package :sb-concurrency)
31 (defconstant +dummy+ '.dummy.)
32 (defconstant +dead-end+ '.dead-end.)
34 (declaim (inline %make-queue))
35 (defstruct (queue (:constructor %make-queue (head tail name))
38 "Lock-free thread safe FIFO queue.
40 Use ENQUEUE to add objects to the queue, and DEQUEUE to remove them."
41 (head (error "No HEAD.") :type cons)
42 (tail (error "No TAIL.") :type cons)
45 (setf (documentation 'queuep 'function)
46 "Returns true if argument is a QUEUE, NIL otherwise."
47 (documentation 'queue-name 'function)
48 "Name of a QUEUE. Can be assignned to using SETF. Queue names
49 can be arbitrary printable objects, and need not be unique.")
51 (defun make-queue (&key name initial-contents)
52 "Returns a new QUEUE with NAME and contents of the INITIAL-CONTENTS
54 (let* ((dummy (cons +dummy+ nil))
55 (queue (%make-queue dummy dummy name)))
58 (declare (dynamic-extent #'enc-1))
59 (map nil #'enc-1 initial-contents))
62 (defun enqueue (value queue)
63 "Adds VALUE to the end of QUEUE. Returns VALUE."
64 ;; Attempt CAS, repeat upon failure. Upon success update QUEUE-TAIL.
65 (declare (optimize speed))
66 (let ((new (cons value nil)))
67 (loop (when (eq nil (sb-ext:compare-and-swap (cdr (queue-tail queue))
69 (setf (queue-tail queue) new)
72 (defun dequeue (queue)
73 "Retrieves the oldest value in QUEUE and returns it as the primary value,
74 and T as secondary value. If the queue is empty, returns NIL as both primary
76 ;; Attempt to CAS QUEUE-HEAD with the next node, repeat upon
77 ;; failure. Upon success, clear the discarded node and set the CAR
78 ;; of QUEUE-HEAD to +DUMMY+.
79 (declare (optimize speed))
80 (loop (let* ((head (queue-head queue))
82 ;; NEXT could be +DEAD-END+, whereupon we try again.
84 (null (return (values nil nil)))
85 (cons (when (eq head (sb-ext:compare-and-swap (queue-head queue)
87 (let ((value (car next)))
88 ;; Clear the CDR, otherwise the conservative GC could
89 ;; hoard long lists. (car head) is always +dummy+.
90 (setf (cdr head) +dead-end+
92 (return (values value t)))))))))
94 (defun try-walk-queue (fun queue)
95 ;; This isn't /quite/ as bad as it looks. We're in danger of needing
96 ;; to restart only as long as we're close to the head of the queue.
97 (let ((node (queue-head queue)))
99 (let ((value (car node)))
100 (unless (eq value +dummy+)
101 (funcall fun value)))
102 (setf node (cdr node))
103 (cond ((eq node +dead-end+)
108 (defun list-queue-contents (queue)
109 "Returns the contents of QUEUE as a list without removing them from the
110 QUEUE. Mainly useful for manual examination of queue state, as the list may be
111 out of date by the time it is returned, and concurrent dequeue operations may
112 in the worse case force the queue-traversal to be restarted several times."
116 (unless (try-walk-queue (lambda (elem) (result elem)) queue)
118 (return-from list-queue-contents (result)))))
120 (defun queue-count (queue)
121 "Returns the number of objects in QUEUE. Mainly useful for manual
122 examination of queue state, and in PRINT-OBJECT methods: inefficient as it
123 must walk the entire queue."
127 (unless (try-walk-queue (lambda (elem)
128 (declare (ignore elem))
132 (return-from queue-count count))))
134 (defun queue-empty-p (queue)
135 "Returns T if QUEUE is empty, NIL otherwise."
136 (null (cdr (queue-head queue))))