sync.Map
is a concurrent-safe map implementation. the type of raw maps in sync.Map
actually is map[any]*entry
.
When we call Map.LoadOrStore
and the entry exists, entry.tryLoadOrStore
is invoked and the following is the code of this function
func (e *entry) tryLoadOrStore(i any) (actual any, loaded, ok bool) {
p := e.p.Load()
if p == expunged {
return nil, false, false
}
if p != nil {
return *p, true, true
}
// Copy the interface after the first load to make this method more amenable
// to escape analysis: if we hit the "load" path or the entry is expunged, we
// shouldn't bother heap-allocating.
ic := i
for {
if e.p.CompareAndSwap(nil, &ic) {
return i, false, true
}
p = e.p.Load()
if p == expunged {
return nil, false, false
}
if p != nil {
return *p, true, true
}
}
}
And this is another function trySwap
, when we call Swap
or Store
, this function is also invoked.
func (e *entry) trySwap(i *any) (*any, bool) {
for {
p := e.p.Load()
if p == expunged {
return nil, false
}
if e.p.CompareAndSwap(p, i) {
return p, true
}
}
}
tryLoadOrStore
can be implemented like trySwap
just based on its logic but it doesn't. Here is my question: since their logic is similar, why they are not implemented in same way?
when I try to understand, I think its because the difference of parameter type, if I
is *any
, it doesn't need to do a copy since it's already a pointer and we does not need to care about the escape analysis. But there seems to be no special reason to get address from the outer caller.
if e, ok := read.m[key]; ok {
if v, ok := e.trySwap(&value); ok {
if v == nil {
return nil, false
}
return *v, true
}
}
Then I have no idea why this two functions (and other functions) are implemented in different way.