1
0
mirror of https://github.com/golang/go synced 2024-11-11 19:11:35 -07: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:
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) _, _ = 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 // 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 // it with a new value if the entry is equal to the old value, and the entry
// has not been expunged. // 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. // mapInterface is the interface Map implements.
type mapInterface interface { type mapInterface interface {
Load(any) (any, bool) Load(key any) (value any, ok bool)
Store(key, value any) Store(key, value any)
LoadOrStore(key, value any) (actual any, loaded bool) LoadOrStore(key, value any) (actual any, loaded bool)
LoadAndDelete(key any) (value any, loaded bool) LoadAndDelete(key any) (value any, loaded bool)
@ -22,6 +22,7 @@ type mapInterface interface {
CompareAndSwap(key, old, new any) (swapped bool) CompareAndSwap(key, old, new any) (swapped bool)
CompareAndDelete(key, old any) (deleted bool) CompareAndDelete(key, old any) (deleted bool)
Range(func(key, value any) (shouldContinue bool)) Range(func(key, value any) (shouldContinue bool))
Clear()
} }
var ( 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 // DeepCopyMap is an implementation of mapInterface using a Mutex and
// atomic.Value. It makes deep copies of the map on every write to avoid // atomic.Value. It makes deep copies of the map on every write to avoid
// acquiring the Mutex in Load. // acquiring the Mutex in Load.
@ -269,3 +277,10 @@ func (m *DeepCopyMap) dirty() map[any]any {
} }
return dirty 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") opSwap = mapOp("Swap")
opCompareAndSwap = mapOp("CompareAndSwap") opCompareAndSwap = mapOp("CompareAndSwap")
opCompareAndDelete = mapOp("CompareAndDelete") opCompareAndDelete = mapOp("CompareAndDelete")
opClear = mapOp("Clear")
) )
var mapOps = [...]mapOp{ var mapOps = [...]mapOp{
@ -37,6 +38,7 @@ var mapOps = [...]mapOp{
opSwap, opSwap,
opCompareAndSwap, opCompareAndSwap,
opCompareAndDelete, opCompareAndDelete,
opClear,
} }
// mapCall is a quick.Generator for calls on mapInterface. // mapCall is a quick.Generator for calls on mapInterface.
@ -74,6 +76,9 @@ func (c mapCall) apply(m mapInterface) (any, bool) {
} }
} }
return nil, false return nil, false
case opClear:
m.Clear()
return nil, false
default: default:
panic("invalid mapOp") panic("invalid mapOp")
} }
@ -294,3 +299,61 @@ func TestMapRangeNoAllocations(t *testing.T) { // Issue 62404
t.Errorf("AllocsPerRun of m.Range = %v; want 0", allocs) 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)
}
}