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