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.