Go does not have atomic variables, only atomic access to variables
Suppose, hypothetically, that you have a structure full of expvar
variables. You would like to expose
all of them in one operation with expvar.Func
, using some code that
goes roughly like this:
var events struct { Var1, Var2, Var3 expvar.Int } func ReportStats() interface{} { return events }
Ignoring for the moment how this won't work, let's ask a more fundamental question: is this a safe, race-free operation?
On first blush it looks like it should be, since the expvar types are all safe for concurrent access through their methods. However, this is actually not the case, due to an important thing about Go:
Go does not have atomic variables, only atomic access to variables.
Some languages support special atomic variable types. These variable
types are defined so that absolutely all (safe) language access to the
variables that you can perform is atomic, even mundane accesses like
'acopy = avar
' or 'return avar
'. In such a language, ReportStats()
would be safe.
Go is not such a language. Go has no atomic variable types; instead,
all it has is atomic access to ordinary non-atomic variables (through
the sync/atomic
package).
This means that language level operations like 'acopy = avar
' or
'return avar
' are not atomic and are not protected against various
sorts of data races that create inconsistencies or other dangers.
The expvar types are no exception to this; their public methods are
concurrency safe (which is achieved in various ways), but the actual
underlying unexported fields inside them are not safe if you do
things like make copies of them, as ReportStats()
does when it
says 'return events
'.
In some cases you can get a warning about this, as go vet
will
complain about the related issue of making a copy of a sync
lock (or anything containing one,
such as a sync.RWMutex
). Some types that are intended to be
accessed only atomically will have an embedded lock, and so making
a copy of them will cause go vet
to complain about copying their
embedded lock. However, not all 'intended to be atomic' types use
embedded locks, so not all will be caught by this check; for example,
expvar.String
has an embedded lock (in a sync.RWMutex
) and so
will provoke go vet
complaints when copied, but expvar.Int
currently doesn't have an embedded lock and go vet
will not warn
you if you copy one and give yourself a potential data race.
(There may someday be a way to annotate types so that go vet
knows
to complain about making copies of them, but as far as I know there's
no way to do that today. If such a way is added, presumably all
expvar
variable types would get that annotation.)
|
|