Paste number 79013: Less lockful RW lock

Index of paste annotations: 1

Paste number 79013: Less lockful RW lock
Pasted by: pkhuong
When:1 year, 4 months ago
Share:Tweet this! | http://paste.lisp.org/+1OYT
Channel:#lisp
Paste contents:
Raw Source | XML | Display As
I don't have a proof that it works or a counter-example yet.
Seems sane and is fairly simple, but I'm not convinced enough
of the correctness to use it yet (:

(defstruct (rw-lock
             (:conc-name #:rw-lock.))
  (mutex     (sb-thread:make-mutex :name "rw-lock mutex")
   :type sb-thread:mutex
   :read-only t)
  (can-read  (sb-thread:make-waitqueue :name "rw-lock read queue")
   :type sb-thread:waitqueue
   :read-only t)
  (can-write (sb-thread:make-waitqueue :name "rw-lock write queue")
   :type sb-thread:waitqueue
   :read-only t)
  ;; number of *current* readers, must be updated atomically
  ;; value is even if no writer is waiting,
  ;;  odd (inc'ed) if some are waiting/active
  ;; oddness only modified with lock held!
  (reader-count 0 :type (unsigned-byte #.sb-vm:n-word-bits))
  ;; number of *waiting* or writing writers, accessed with lock held
  (writer-count 0 :type (unsigned-byte #.sb-vm:n-word-bits)))

;; correctness: Whenever reader-count is modified and becomes 1,
;;  at least 1 writer is notified
;; Threads always increment before decrementing
;; Once reader-count is odd, no read lock is ever acquired.
;; Thus, once reader-count reaches 1, there is no active reader
;;  and never will be until it's back to an even value.
;; When reader-count is 1, there is no active reader or writer.
;; Only 1 writer tries to acquire at a time.

;; When reader-count > 1, writers can't acquire
;; When reader-count is odd, readers can't acquire

;; Performance: readers: 1 LOCK XADD to acquire/release
;;              writers: Lock + 1 LOCK XADD to acquire/release

(defun acquire-read-lock (rw-lock)
  (declare (type rw-lock rw-lock))
  (let ((decp nil))
    (when (or (oddp (rw-lock.reader-count rw-lock))
              (progn
                (setf decp t)
                (oddp (sb-ext:atomic-incf (rw-lock.reader-count rw-lock) 2))))
      (let ((mutex (rw-lock.mutex rw-lock))
            (cvar  (rw-lock.can-read rw-lock)))
        (sb-thread:with-mutex (mutex)
          (when (and decp
                     (= (sb-ext:atomic-incf (rw-lock.reader-count rw-lock) -2)
                        3))
            ;; ABA
            (sb-thread:condition-notify (rw-lock.can-write rw-lock)))
          (loop until (evenp (rw-lock.reader-count rw-lock))
                do (sb-thread:condition-wait cvar mutex))
          ;; Lock is held, even-ness is preserved
          (sb-ext:atomic-incf (rw-lock.reader-count rw-lock) 2))))))

(defun release-read-lock (rw-lock)
  (when (= 3 (sb-ext:atomic-incf (rw-lock.reader-count rw-lock) -2))
    (sb-thread:with-mutex ((rw-lock.mutex rw-lock))
      (sb-thread:condition-notify (rw-lock.can-write rw-lock)))))

(defun acquire-write-lock (rw-lock)
  (let ((mutex (rw-lock.mutex rw-lock))
        (cvar  (rw-lock.can-write rw-lock)))
    (sb-thread:with-mutex (mutex)
      (tagbody
        (when (= 1 (incf (rw-lock.writer-count rw-lock)))
          (when (zerop (sb-ext:atomic-incf (rw-lock.reader-count rw-lock) 1))
            (go DONE)))
        (loop until (= 1 (rw-lock.reader-count rw-lock))
              do (sb-thread:condition-wait cvar mutex))
         DONE
         ;; lock other writers out
        (sb-ext:atomic-incf (rw-lock.reader-count rw-lock) 2)))))

(defun release-write-lock (rw-lock)
  (let ((mutex (rw-lock.mutex rw-lock))
        (cvar  (rw-lock.can-write rw-lock)))
    (sb-thread:with-mutex (mutex)
      (cond ((zerop (decf (rw-lock.writer-count rw-lock)))
             (sb-ext:atomic-incf (rw-lock.reader-count rw-lock) -3)
             (sb-thread:condition-broadcast (rw-lock.can-read rw-lock)))
            (t
             (when (= 3 (sb-ext:atomic-incf (rw-lock.reader-count rw-lock) -2))
               (sb-thread:condition-notify cvar)))))))

Annotations for this paste:

Annotation number 1: comment
Pasted by: mega1
When:1 year, 4 months ago
Share:Tweet this! | http://paste.lisp.org/+1OYT/1
Paste contents:
Raw Source | Display As
It looks fine to me. In an hour or so I could not find holes in it. Could not find visibility bugs either. This is the closest one, but it's still good:

;;; Consider the case where ACQUIRE-READ-LOCK sees a stale, odd reader
;;; count and goes into the body of WHEN. Before acquiring the mutex,
;;; an even reader count becomes visible to this thread. The EVENP
;;; test within the mutex can see a stale value of reader count, but
;;; the test still works because evenness is changed only with the
;;; mutex held.

Colorize as:
Show Line Numbers
Index of paste annotations: 1

Lisppaste pastes can be made by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively.