2011/07/09
Not like the old-school Lisp atoms
Ah it's been long since the last entry. It's not that Gauche development is staggered; the thing is, I run several development threads concurrently, using local topic branches, and it is hard to pick a topic that can make a complete entry with conclusion.
Nah, too much excuses. From now on, I dump here whatever thoughts and experiments I did, slow but constantly, even without a nice conclusion.
Ok, so let me pick up where I left... the last entry was March 16. The next thing I did was to commit the support of atoms.
No, not that traditional Lisp atoms which mean something
that's not a cons.
Having atomp
was once important to explain how Lisp worked,
but these days, with so many different datatypes, I don't think
having a term for non-cons objects is important. That usage
should become a history.
These days, more important atomic property is that something be atomic with regard to concurrent execution. To confess, I steal the term from Clojure. Though Gauche doesn't abstract concurrent threads nor isolate mutable operations so much as Clojure does, I do find some recurring patterns I use while writing concurrent code in Gauche, and this is one of them.
An atom here is a wrapper to bundle one or more objects
that needs to be updated atomically. It is available
in gauche.threads
module. The following creates
an atom with two vectors.
(use gauche.threads) (define a (atom (vector 1) (vector 2)))
a => #<<atom> 0x17467e0>
To access those vectors safely, you pass a procedure to atomic
.
(atomic a (lambda (v0 v1) ...do whatever with v0 and v1...))
No other thread can access/modify the atom a
until
atomic
returns.
Gauche's builtin hashtable is not thread-safe. I was always
wondering whether I should provide a separate thread-safe hashtable
type,
or provide a generic support to allow exclusive access to any
objects, like Java's synchronized
mechanism. The former
would explode the number of primitive datatypes
(how about thread-safe treemap? thread-safe sparse hash? &c.)
The latter is cleaner, but difficult to implement efficiently
(especially, think about allow synchronized
on any pair!)
Now atoms can be used to provide mutable objects exclusive access.
Just say (atom (make-hashtable 'eq?))
, and then
wrap any access to the table by atomic
. I expect that
most cases of trivial use of <mutex>
can be replaced
by atoms.
Not only wrapping mutable objects, but atom itself can be
a mutable objects with synchronization. You can replace
its wrapped value by atomic-update!
.
(define b (atom 0))
(atomic-update! b (cut + 1 <>))
This makes an atom b works as a counter with mutex. If the
atom wraps N values, the update procedure should be N-in N-out
procedure. To extract the value, a convenient procedure
atom-ref
can be used.
(atom-ref b)
You may notice that if you retrieve a mutable object wrapped
by atom by atom-ref
, you can modify it outside of protection.
Atoms are not intended to prohibit you from doing dangerous things;
they just make it easier to do things safely rather than error-prone ways.
Finally but importantly, those atom primitive consistently takes optional timeout and timeout-val arguments.
(atomic a (^(x y) ... #t) 10.0 'timeout)
This returns #t if we can access the atom successfully,
but if we cannot obtain a lock for 10.0 seconds, it gives up
and returns timeout
. I find it indispensable to
write a robust code. The semantics of timeout and timeout-val
is the same as other thread primitives.
Tags: atom, gauche.threads, Concurrency
Post a comment