mirror of
https://github.com/golang/go
synced 2024-09-23 13:20:14 -06:00
sync: add Map.Clear
Fixes #61696
Change-Id: I0a31afd3bc433fc84280d56f2798bda10da61eba
GitHub-Last-Rev: 17bedc864f
GitHub-Pull-Request: golang/go#61702
Reviewed-on: https://go-review.googlesource.com/c/go/+/515015
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Reviewed-by: qiulaidongfeng <2645477756@qq.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
2f6a25f447
commit
5b6cd3d0cb
1
api/next/61696.txt
Normal file
1
api/next/61696.txt
Normal file
@ -0,0 +1 @@
|
||||
pkg sync, method (*Map) Clear() #61696
|
@ -155,6 +155,26 @@ func (m *Map) Store(key, value any) {
|
||||
_, _ = m.Swap(key, value)
|
||||
}
|
||||
|
||||
// Clear deletes all the keys.
|
||||
func (m *Map) Clear() {
|
||||
read := m.loadReadOnly()
|
||||
if len(read.m) == 0 && !read.amended {
|
||||
// Avoid allocating a new readOnly when the map is already clear.
|
||||
return
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
read = m.loadReadOnly()
|
||||
if len(read.m) > 0 || read.amended {
|
||||
m.read.Store(&readOnly{})
|
||||
}
|
||||
|
||||
clear(m.dirty)
|
||||
m.misses = 0 // Don't immediately promote the newly-cleared dirty map on the next operation
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
@ -533,3 +533,15 @@ func BenchmarkCompareAndDeleteMostlyMisses(b *testing.B) {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkClear(b *testing.B) {
|
||||
benchMap(b, bench{
|
||||
perG: func(b *testing.B, pb *testing.PB, i int, m mapInterface) {
|
||||
for ; pb.Next(); i++ {
|
||||
k, v := i%256, i%256
|
||||
m.Clear()
|
||||
m.Store(k, v)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import (
|
||||
|
||||
// mapInterface is the interface Map implements.
|
||||
type mapInterface interface {
|
||||
Load(any) (any, bool)
|
||||
Load(key any) (value any, ok bool)
|
||||
Store(key, value any)
|
||||
LoadOrStore(key, value any) (actual any, loaded bool)
|
||||
LoadAndDelete(key any) (value any, loaded bool)
|
||||
@ -22,6 +22,7 @@ type mapInterface interface {
|
||||
CompareAndSwap(key, old, new any) (swapped bool)
|
||||
CompareAndDelete(key, old any) (deleted bool)
|
||||
Range(func(key, value any) (shouldContinue bool))
|
||||
Clear()
|
||||
}
|
||||
|
||||
var (
|
||||
@ -144,6 +145,13 @@ func (m *RWMutexMap) Range(f func(key, value any) (shouldContinue bool)) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *RWMutexMap) Clear() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
clear(m.dirty)
|
||||
}
|
||||
|
||||
// DeepCopyMap is an implementation of mapInterface using a Mutex and
|
||||
// atomic.Value. It makes deep copies of the map on every write to avoid
|
||||
// acquiring the Mutex in Load.
|
||||
@ -269,3 +277,10 @@ func (m *DeepCopyMap) dirty() map[any]any {
|
||||
}
|
||||
return dirty
|
||||
}
|
||||
|
||||
func (m *DeepCopyMap) Clear() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.clean.Store((map[any]any)(nil))
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ const (
|
||||
opSwap = mapOp("Swap")
|
||||
opCompareAndSwap = mapOp("CompareAndSwap")
|
||||
opCompareAndDelete = mapOp("CompareAndDelete")
|
||||
opClear = mapOp("Clear")
|
||||
)
|
||||
|
||||
var mapOps = [...]mapOp{
|
||||
@ -37,6 +38,7 @@ var mapOps = [...]mapOp{
|
||||
opSwap,
|
||||
opCompareAndSwap,
|
||||
opCompareAndDelete,
|
||||
opClear,
|
||||
}
|
||||
|
||||
// mapCall is a quick.Generator for calls on mapInterface.
|
||||
@ -74,6 +76,9 @@ func (c mapCall) apply(m mapInterface) (any, bool) {
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
case opClear:
|
||||
m.Clear()
|
||||
return nil, false
|
||||
default:
|
||||
panic("invalid mapOp")
|
||||
}
|
||||
@ -294,3 +299,61 @@ func TestMapRangeNoAllocations(t *testing.T) { // Issue 62404
|
||||
t.Errorf("AllocsPerRun of m.Range = %v; want 0", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrentClear tests concurrent behavior of sync.Map properties to ensure no data races.
|
||||
// Checks for proper synchronization between Clear, Store, Load operations.
|
||||
func TestConcurrentClear(t *testing.T) {
|
||||
var m sync.Map
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(30) // 10 goroutines for writing, 10 goroutines for reading, 10 goroutines for waiting
|
||||
|
||||
// Writing data to the map concurrently
|
||||
for i := 0; i < 10; i++ {
|
||||
go func(k, v int) {
|
||||
defer wg.Done()
|
||||
m.Store(k, v)
|
||||
}(i, i*10)
|
||||
}
|
||||
|
||||
// Reading data from the map concurrently
|
||||
for i := 0; i < 10; i++ {
|
||||
go func(k int) {
|
||||
defer wg.Done()
|
||||
if value, ok := m.Load(k); ok {
|
||||
t.Logf("Key: %v, Value: %v\n", k, value)
|
||||
} else {
|
||||
t.Logf("Key: %v not found\n", k)
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
// Clearing data from the map concurrently
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
m.Clear()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
m.Clear()
|
||||
|
||||
m.Range(func(k, v any) bool {
|
||||
t.Errorf("after Clear, Map contains (%v, %v); expected to be empty", k, v)
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func TestMapClearNoAllocations(t *testing.T) {
|
||||
testenv.SkipIfOptimizationOff(t)
|
||||
var m sync.Map
|
||||
allocs := testing.AllocsPerRun(10, func() {
|
||||
m.Clear()
|
||||
})
|
||||
if allocs > 0 {
|
||||
t.Errorf("AllocsPerRun of m.Clear = %v; want 0", allocs)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user