The Go's map semantics are as follows:
-
A map stores values (not variables) and that's why these values are
not adressable, and that's why you can't do something like
type T struct {
X int
}
m := make(map[int]T)
m[0] = T{}
m[0].x = 42 // won't compile
This requirement mostly comes from from the fact a map, being
an intricate dynamic data structure, should allow its particular
implementations to physically move the values it contains
around in memory — when doing rebalancing etc.
That's why the only three operations a map supports is adding
(or replacing) of its elements, getting them back and deleting them.
A map is not safe for concurrent use, so in order to do any of those three
operations on the same map concurrently, you need to protect it in
one way or another.
Consequently, once you have read a value from a map, orchestrating
concurrent access to it is a completely another story,
and here we're facing another fact about the map's semantics:
since it keeps values and is free to copy them around in memory,
it's not allowed to keep in a map anything which you want to have
reference semantics. For instance, it would be incorrect to keep values
of your Stats
type in the map directly—because they embed instances
of sync.Mutex
, and copying of them prohibited after they are first used.
Here you're already doing the right thing by storing pointers to your
variables.
Now you can see that it's pretty OK to roll like this:
- Access the map itself to get a value bound to a key in a concurrent-safe
way (say, by holding a lock).
- Lock the mutex on that variable and operate on it. That does not
involve the map at all.
The only remaining possible problem is as follows.
Suppose you're protecting the access to your map with a lock.
So you grab the lock, obtain the value bound to a key, by copying
it to a variable, release the lock and work with the copy of the
value.
Now while you're working with the copy of that value another
goroutine is free to update the map by deleting the value or replacing it.
While in your case it's fine technically — because your map operates on
pointers to variables, and it's fine to copy pointers — this might be inappropriate from the standpoint of the semantics of your program,
and this is something you have to think through.
To make it more clear, once you've got a pointer to some instance of Stats
and locked it, a pointer to this instance can be removed from the map,
or the map entry which held it could be updated by another pointer —
pointing to another instance of Stats
, so once you're done with the
instance, it might have become unreachable via the map.