| 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: |
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: |
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.