diff options
Diffstat (limited to 'cmap/map.go')
-rw-r--r-- | cmap/map.go | 104 |
1 files changed, 5 insertions, 99 deletions
diff --git a/cmap/map.go b/cmap/map.go index bfc0070..7a1fe5b 100644 --- a/cmap/map.go +++ b/cmap/map.go @@ -14,7 +14,7 @@ import ( "unsafe" ) -// Map[K comparable, V comparable] is like a Go map[K]V but is safe for concurrent use +// Map[K comparable, V any] is like a Go map[K]V but is safe for concurrent use // by multiple goroutines without additional locking or coordination. Loads, // stores, and deletes run in amortized constant time. // @@ -37,7 +37,7 @@ import ( // and [Map.CompareAndDelete] is a write operation when it returns deleted set to true. // // [the Go memory model]: https://go.dev/ref/mem -type Map[K comparable, V comparable] struct { +type Map[K comparable, V any] struct { mu sync.Mutex // read contains the portion of the map's contents that are safe for @@ -73,7 +73,7 @@ type Map[K comparable, V comparable] struct { } // readOnly is an immutable struct stored atomically in the Map.read field. -type readOnly[K comparable, V comparable] struct { +type readOnly[K comparable, V any] struct { m map[K]*entry[V] amended bool // true if the dirty map contains some key not in m. } @@ -83,7 +83,7 @@ type readOnly[K comparable, V comparable] struct { var expunged = unsafe.Pointer(new(any)) // An entry is a slot in the map corresponding to a particular key. -type entry[V comparable] struct { +type entry[V any] struct { // p points to the value stored for the entry. // // If p == nil, the entry has been deleted, and either m.dirty == nil or @@ -106,7 +106,7 @@ type entry[V comparable] struct { p unsafe.Pointer } -func newEntry[V comparable](i V) *entry[V] { +func newEntry[V any](i V) *entry[V] { return &entry[V]{p: unsafe.Pointer(&i)} } @@ -179,33 +179,6 @@ func (m *Map[K, V]) Clear() { m.misses = 0 } -// tryCompareAndSwap compare the entry with the given old value and swaps -// it with a new value if the entry is equal to the old value, and the entry -// has not been expunged. -// -// If the entry is expunged, tryCompareAndSwap returns false and leaves -// the entry unchanged. -func (e *entry[V]) tryCompareAndSwap(old V, new V) bool { - p := atomic.LoadPointer(&e.p) - if p == nil || p == expunged || *(*V)(p) != old { // XXX - return false - } - - // Copy the pointer after the first load to make this method more amenable - // to escape analysis: if the comparison fails from the start, we shouldn't - // bother heap-allocating a pointer to store. - nc := new - for { - if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(&nc)) { - return true - } - p = atomic.LoadPointer(&e.p) - if p == nil || p == expunged || *(*V)(p) != old { - return false - } - } -} - // unexpungeLocked ensures that the entry is not marked as expunged. // // If the entry was previously expunged, it must be added to the dirty map @@ -392,73 +365,6 @@ func (m *Map[K, V]) Swap(key K, value V) (previous V, loaded bool) { return previous, loaded } -// 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. -func (m *Map[K, V]) CompareAndSwap(key K, old, new V) (swapped bool) { - read := m.loadReadOnly() - if e, ok := read.m[key]; ok { - return e.tryCompareAndSwap(old, new) - } else if !read.amended { - return false // No existing value for key. - } - - m.mu.Lock() - defer m.mu.Unlock() - read = m.loadReadOnly() - swapped = false - if e, ok := read.m[key]; ok { - swapped = e.tryCompareAndSwap(old, new) - } else if e, ok := m.dirty[key]; ok { - swapped = e.tryCompareAndSwap(old, new) - // We needed to lock mu in order to load the entry for key, - // and the operation didn't change the set of keys in the map - // (so it would be made more efficient by promoting the dirty - // map to read-only). - // Count it as a miss so that we will eventually switch to the - // more efficient steady state. - m.missLocked() - } - return swapped -} - -// 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 a nil pointer). -func (m *Map[K, V]) CompareAndDelete(key K, old V) (deleted bool) { - read := m.loadReadOnly() - e, ok := read.m[key] - if !ok && read.amended { - m.mu.Lock() - read = m.loadReadOnly() - e, ok = read.m[key] - if !ok && read.amended { - e, ok = m.dirty[key] - // Don't delete key from m.dirty: we still need to do the “compare” part - // of the operation. The entry will eventually be expunged when the - // dirty map is promoted to the read map. - // - // Regardless of whether the entry was present, record a miss: this key - // will take the slow path until the dirty map is promoted to the read - // map. - m.missLocked() - } - m.mu.Unlock() - } - for ok { - p := atomic.LoadPointer(&e.p) - if p == nil || p == expunged || *(*V)(p) != old { - return false - } - if atomic.CompareAndSwapPointer(&e.p, p, nil) { - return true - } - } - return false -} - // Range calls f sequentially for each key and value present in the map. // If f returns false, range stops the iteration. // |