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:
Hiro 2024-01-03 00:10:58 +00:00 committed by Gopher Robot
parent 2f6a25f447
commit 5b6cd3d0cb
5 changed files with 112 additions and 1 deletions

1
api/next/61696.txt Normal file
View File

@ -0,0 +1 @@
pkg sync, method (*Map) Clear() #61696

View File

@ -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.

View File

@ -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)
}
},
})
}

View File

@ -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))
}

View File

@ -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)
}
}