| I've been reading through the sources of ac.scm in an attempt to duplicate some of its functionality for Arcueid and noticed that atomic-invoke actually provides a much tighter lock than one might expect from the name. It would appear that in Arc3 atomic-invoke uses a single global mutex for all invocations, locking this mutex whenever any thunk is executed by atomic-invoke, and releasing it when the thunk exits. I suppose that means that at any given time only one thread protected by atomic-invoke may be running at any given time. One might have thought that every use of atomic-invoke would produce its own mutex, so only atomic-invokes on the same thunk would be thus restricted, but it would seem not. I thus managed to very easily implement it in Arcueid's arc.arc using a global synchronization channel as follows: (def atomic-invoke (thunk)
(if (isnt (type thunk) 'fn) (err "atomic-invoke requires fn arg")
(__acell__) (thunk)
(do (__acell__ t)
(<-= (__achan__) t)
(protect (fn () (thunk))
(fn () (<- (__achan__))
(__acell__ nil))))))
The internal function __acell__ retrieves/sets a per-thread cell with a true or nil value depending on whether the thread holds the atomic channel. __achan__ retrieves the global synchronization channel. The <- function reads from the channel, blocking if there is nothing to read and resuming when something can be read, The <-= function writes to the channel, blocking if something was already written there before, and resuming once the channel has been read from.*It seems that this behaves almost like a global interpreter lock, and removing it will increase concurrency at the possible expense of having more bookkeeping overhead. I see that atomic-invoke is used by practically everything in arc.arc that manipulates places and setforms (in the form of atwith and atwiths), and I get the feeling that replacing this with finer-grained locks (which would not be hard for Arcueid to do and probably not at all difficult to do in ac.scm for that matter) would cause bookkeeping overhead to skyrocket in the same way that attempts to remove the GIL in Python had. Well, why not remove such usage of atwith and atwiths inside places and setforms, and note that one must not assume that assignments made that way are atomic, and let the programmer use atomic-invoke or other more flexible synchronization primitives oneself where this is desirable? Or better yet, provide alternate versions of =, swap, push, rotate, etc. that are guaranteed atomic? I suppose this is much more in keeping with Arc's philosophy that the programmer is a smart person, and should be aware that threads are in that way fraught with peril. ---- * The idea of using such channels for synchronization has been shamelessly stolen from C.A.R. Hoare's CSP and the notation of <- and <-= has in turn been just as shamelessly stolen from the Limbo programming language used in the Bell Labs Inferno OS. It essentially implements an idea I sketched here long ago: http://www.arclanguage.org/item?id=1823. |