Reputation: 1278
Ok, for some simplified setup, in this example we have three structs comp
, agg
and cache
that look somewhat like this:
type comp struct {
id uint64
val float64
}
type agg struct {
id uint64
vals []*comp
}
type cache struct {
compMtx sync.RWMutex
comps map[uint64]*comp
aggMtx sync.RWMutex
aggs map[uint64]*agg
}
Where the cache
has the following function to add new comp
values, which in the case of the update does not seem to work:
func (c *cache) NewComp(cpNew *comp) {
compMtx.Lock()
defer compMtx.Unlock()
cpOld, ok := c.comps[cpNew.id]
if ok { // update
addr := &cpOld // this is of type **comp
*addr = cpNew
} else { // new value
c.comps[cpNew.id] = cpNew
}
}
The thought behind this method is that by changing where the pointer of the pointer points to, we can ensure that the comp
pointers in agg.vals
always point to the newest iteration of a given comp
object.
As for the reason behind this approach, iterating over the entirety of an agg.vals
array to find the index of the given comp
object would a) be computationally expensive due to the (rather large) size of the array and would b) require agg
to be locked via an internal sync.Mutex
during the search to block different threads from accessing the object, both of which are undesirable. Furthermore assume that making agg.value
into a map so as to facilitate easy updates is not a possibility.
Since NewComp
as implemented above does however not work, my question would be whether there are any obvious mistakes in the function above, or if I made some fundamental error in my thinking here?
As this might be helpful, here also an example in which the update via pointers of pointers works as expected:
type wrks struct {
id uint64
val1 *comp
val2 *comp
}
func compute() *comp {...}
func updateComp(c **comp) {
*c = compute()
}
func processObj(o *obj) {
updateComp(&o.val1)
updateComp(&o.val2)
}
I fail to see the fundamental difference between the two, but I may have been staring at this for too long at this point.
Upvotes: 2
Views: 221
Reputation: 417472
You store pointers in the map, so when you get a pointer from it, just modify the pointed value, assign to the pointed value:
cpOld, ok := c.comps[cpNew.id]
if ok { // update
*cpOld = *cpNew
} else { // new value
c.comps[cpNew.id] = cpNew
}
See a simplified example on the Go Playground that shows this works.
Your original code doesn't work because cpOld
is a pointer, but it's a copy of the pointer stored in the map. If you modify the value of this cpOld
variable, that has no effect on the value stored in the map. You can't change values in a map, you can only reassign / store new values in them.
One thing you should keep in mind: the cpNew
pointer passed to NewComp()
will not be "useful" after the update, because we didn't use the pointer, we just used the pointed value to update the value that was already pointed by the map (a value stored in the map). If you want the pointer to keep pointing to the same value, you have no other choice but to store that pointer in the map, like if it was new:
cpOld := c.comps[cpNew.id] = cpNew
See related question: How to update map values in Go
Upvotes: 1