-
Notifications
You must be signed in to change notification settings - Fork 17.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sync: add new Map methods CompareAndSwap, CompareAndDelete, Swap #51972
Comments
CC @bcmills |
This proposal has been added to the active column of the proposals project |
@bcmills, what do you think? |
It has the right granularity to fit with the other |
We may also want to consider // CompareAndSwap swaps the old and new values for key
// if the value stored in the map is equal to old.
// The old value must be of a comparable type.
//
// If old is the nil interface value, the swap will occur if either there
// is no existing value for the key or the existing value is also the
// nil interface.
func (m *Map) CompareAndSwap(key, old, new any) (swapped bool)
// CompareAndDelete deletes the entry for key if its value is equal to old.
// The old value must be of a comparable type.
//
// If there is no current value for key in the map, CompareAndDelete
// returns false (even if the old value is the nil interface value).
func (m *Map) CompareAndDelete(key, old any) (deleted bool) |
Change https://go.dev/cl/399094 mentions this issue: |
Does anyone object to adding Swap, CompareAndSwap, and CompareAndDelete? |
While prototyping the CompareAndDelete, it got me thinking: is it really make sense to have
Since we don't have an atomic compare and delete primitive. What can we do to the read map to compare the old value, and delete it atomically? I could not yet make sense out of this implementation: func (m *Map) CompareAndDelete(key, old any) (deleted bool) {
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
if e.tryCompareAndSwap(&old, nil) {
// Here is clearly(?) non-atomic
// what if something happens between the above CAS and below delete?
// Is it possible?
delete(read.m, key)
deleted = true
return
}
}
m.mu.Lock()
...
} Or is this something I am not going in the right direction of implementation? It looks like to me that we have to lock the entire Map to proceed? Am I overthinking here? What is the fast path in this case? |
@changkun, the use case for If the key is a hit in the read-only map, the entry will still remain but be marked as deleted. |
Based on the discussion above, this proposal seems like a likely accept. |
No change in consensus, so accepted. 🎉 |
In #51972 (comment) I suggested that a Here's my thinking: if we use a stricter func StoreOrCompareAndSwapNil(m *sync.Map, key, new interface) (swapped bool) {
old, loaded := m.LoadOrStore(key, new)
if !loaded {
return true // Swapped new against “no key”.
}
if old != nil {
return false // Old value was not the nil interface value.
}
// Try to swap new against an explicit, existing nil.
return m.CompareAndSwap(key, old, new)
} The reverse does not hold, so the stricter Moreover, the |
The stricter |
If a generic version of For example, this is the signature I would expect for a generic map: type Map[K comparable, V any] struct { ... } Given that signature, the following methods are not type safe: func (m *Map[K, V]) CompareAndSwap(key K, old, new V) (swapped bool)
func (m *Map[K, V]) CompareAndDelete(key K, old V) (deleted bool) However, we could do: func CompareAndSwap[K, V comparable](m *Map[K, V], key K, old, new V) (swapped bool)
func CompareAndDelete[K, V comparable](m *Map[K, V], key K, old V) (deleted bool) where it forces the caller to provide a generic |
What was the conclusion by #48287? When did it close? How will it impact the design of a generic |
#48287 is not yet resolved. There is no conclusion. |
Thinking about the matrix of operations, it appears to be complete: we now have operations that support atomically changing a map entry from any one of {no value, any value, specific value} to any one of { value, no value } in a way that allows the caller to confirm the state of the entry prior to the change.
|
Change https://go.dev/cl/450797 mentions this issue: |
For #51972. Change-Id: I86dcd8abc3b62e20b524541327af2cc891cb251d Reviewed-on: https://go-review.googlesource.com/c/go/+/450797 Reviewed-by: Ian Lance Taylor <iant@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Bryan Mills <bcmills@google.com> Run-TryBot: Bryan Mills <bcmills@google.com>
Change https://go.dev/cl/459715 mentions this issue: |
CL 381316 documented the memory model of Map's APIs. However, the newly introduced Swap, CompareAndSwap, and CompareAndDelete are missing from this documentation as CL 399094 did not add this info. This CL specifies the defined read/write operations of the new Map APIs. For #51972 Change-Id: I519a04040a0b429a3f978823a183cd62e42c90ae Reviewed-on: https://go-review.googlesource.com/c/go/+/459715 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Run-TryBot: Changkun Ou <mail@changkun.de> Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Bryan Mills <bcmills@google.com>
Change https://go.dev/cl/463416 mentions this issue: |
…nd{Swap,Delete} in Map CL 381316 documented the memory model of Map's APIs. However, the newly introduced Swap, CompareAndSwap, and CompareAndDelete are missing from this documentation as CL 399094 did not add this info. This CL specifies the defined read/write operations of the new Map APIs. For #51972 Change-Id: I519a04040a0b429a3f978823a183cd62e42c90ae Reviewed-on: https://go-review.googlesource.com/c/go/+/459715 TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Run-TryBot: Changkun Ou <mail@changkun.de> Auto-Submit: Bryan Mills <bcmills@google.com> Reviewed-by: Bryan Mills <bcmills@google.com> (cherry picked from commit f07910b) Reviewed-on: https://go-review.googlesource.com/c/go/+/463416 Run-TryBot: Matthew Dempsky <mdempsky@google.com> Auto-Submit: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Changkun Ou <mail@changkun.de>
I propose adding a new method on
sync.Map
similar toatomic.Value
'sSwap()
method. I think either the nameLoadAndStore()
to match the existing LoadOrStore orSwap()
to matchatomic.Value
would make sense. The function signature would look something like thisI think the fact that this already exists in
atomic.Value
is a good argument that there is a use case for it. The same thing could be achieved by creating async.Map
ofatomic.Value
s, but that is a lot of type-assertion, and I have to stare at it pretty hard to make sure it's free of race conditions. My specific use case is basically de-bouncing abuse reports. If a worker detects abuse from a client it wouldThe text was updated successfully, but these errors were encountered: