Multiple CAS (MCAS) versus Locks
Here is a good read on lock-free programming:UCCL Tech Report 579, "Practical lock-freedom" by Keir Fraser, Feb 2004
I adapted his work on MCAS and CCAS to LW, in these (approx) 100 LOC:
On Feb 24, 2017, at 16:18, David McClain <dbm@refined-audiometrics.com> wrote:Here is a good read on lock-free programming:<MCAS-car.lisp>UCCL Tech Report 579, "Practical lock-freedom" by Keir Fraser, Feb 2004I adapted his work on MCAS and CCAS to LW, in these (approx) 100 LOC:Using MCAS, I was able to speed up my Lisp implementation of Reppy Channels from 25-50 kRPC/sec to now 357 kRPC/sec, where an RPC is a dual communication exchange from a client thread to a server thread and receiving back the server response. Each RPC exchange now takes roughly 2.8 microseconds on my Mac Mini.With Reppy Channels, you have the opportunity for thread rendezvous IF you can simultaneously mark both threads with each other’s communication data. If a thread has been previously marked, then no-go. So I needed a dual-CAS instruction. MCAS fits the bill nicely, with room for growth. Just be sure to request multiple-cell CAS with the same object request ordering, to avoid deadlock. I use a sequentially assigned ID in each object to effect my ordering. Address order cannot be relied upon in a GC Lisp heap.With SMP multiprocessing, the use of locks can be a frequent bottleneck. Presumably the speed up occurs because a blocked Lock causes a process thread switch, whereas, even though the code in MCAS is substantial, it pays to keep the CPU’s attention. A factor of 10x in speed up is significant in my accounting.The nice thing about Keir’s work is that these implement not only lock-free CAS, but wait-free too, as other threads gaining on the structure can help your MCAS operation along if it is found pending.Cheers,- DM
And so here is a corrected version of MCAS, including extensions to refer to either the CAR or the CDR of a ref-cell. And along with it, there is an implementation of a fast lock-free/wait-free FIFO queue. All the threads in the world can simultaneously access and update these queues without need for exclusive lock control.
All this work on MCAS has me looking for other good applications of it. So this morning I sat down to do a Read/Write Gate. Along the way, I pushed myself for minimalism, and found that MCAS is never needed there… Here is a sort of minimalist Read/Write Gate (many readers, one writer) that is recursive and SMP ready, using only a single CAS instruction. Any number of RW Gates can be set up as needed. About 100 LOC.
um… yeah… (red face) Here is a corrected version…
Unable to parse email body. Email id is 14270
On Mar 1, 2017, at 07:44, Martin Simmons <martin@lispworks.com> wrote:Hi David,
Unfortunately there is a problem with rwg-cas v.s. rwg-release. Suppose the
following sequence of events occurs:
0. Initially *gate* is free, i.e. (nil . 0).
1. Thread A evaluates (rwg-cas *gate* :read) and then (rwg-release *gate*),
which reaches the start of the (setf (car gate) nil). *gate* is now
(t . 0).
2. Thread B evaluates (rwg-cas *gate* :read), which succeeds and *gate* is now
(t . 1).
3. Thread A evaluates the setf and *gate* is now (nil . 1). This is an
illegal state because it would allow a write lock to be taken even though
thread B still has a read lock.
--
Martin Simmons
LispWorks Ltd
http://www.lispworks.com/
_______________________________________________
Lisp Hug - the mailing list for LispWorks users
lisp-hug@lispworks.com
http://www.lispworks.com/support/lisp-hug.html
On Mar 1, 2017, at 09:08, David McClain <dbm@refined-audiometrics.com> wrote:Hi Martiin,Yes indeed… I think this fixes things…(defun make-rwgate ()(list 0))(defun rwg-cas (gate locktype)(let ((new (case locktype(:read t)(:write mp:*current-process*))))(and (sys:compare-and-swap (cdr gate)(and (cdr gate)new)new)(sys:atomic-fixnum-incf (car gate)))))(defun rwg-release (gate)(let ((v (cdr gate)))(when (zerop (sys:atomic-fixnum-decf (car gate)))(sys:compare-and-swap (cdr gate) v nil))))On Mar 1, 2017, at 07:44, Martin Simmons <martin@lispworks.com> wrote:Hi David,
Unfortunately there is a problem with rwg-cas v.s. rwg-release. Suppose the
following sequence of events occurs:
0. Initially *gate* is free, i.e. (nil . 0).
1. Thread A evaluates (rwg-cas *gate* :read) and then (rwg-release *gate*),
which reaches the start of the (setf (car gate) nil). *gate* is now
(t . 0).
2. Thread B evaluates (rwg-cas *gate* :read), which succeeds and *gate* is now
(t . 1).
3. Thread A evaluates the setf and *gate* is now (nil . 1). This is an
illegal state because it would allow a write lock to be taken even though
thread B still has a read lock.
--
Martin Simmons
LispWorks Ltd
http://www.lispworks.com/
_______________________________________________
Lisp Hug - the mailing list for LispWorks users
lisp-hug@lispworks.com
http://www.lispworks.com/support/lisp-hug.html
Heh! I went back 8 years and, looking through my Okeanos OODBMS code, I found that I (er, rather, LispWorks) had already solved this RW-Gate problem with their shared locks… I giant tip of the hat to Martin and company!! Here it is re-worked from Okeanos with abort cleanup functions as well as timeouts