syncs: add MutexValue (#14422)

MutexValue is simply a value guarded by a mutex.
For any type that is not pointer-sized,
MutexValue will perform much better than AtomicValue
since it will not incur an allocation boxing the value
into an interface value (which is how Go's atomic.Value
is implemented under-the-hood).

Updates #cleanup

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
This commit is contained in:
Joe Tsai
2024-12-18 17:11:22 -08:00
committed by GitHub
parent b3d4ffe168
commit ff5b4bae99
2 changed files with 96 additions and 0 deletions

View File

@ -8,6 +8,7 @@ import (
"io"
"os"
"testing"
"time"
"github.com/google/go-cmp/cmp"
)
@ -65,6 +66,39 @@ func TestAtomicValue(t *testing.T) {
}
}
func TestMutexValue(t *testing.T) {
var v MutexValue[time.Time]
if n := int(testing.AllocsPerRun(1000, func() {
v.Store(v.Load())
v.WithLock(func(*time.Time) {})
})); n != 0 {
t.Errorf("AllocsPerRun = %d, want 0", n)
}
now := time.Now()
v.Store(now)
if !v.Load().Equal(now) {
t.Errorf("Load = %v, want %v", v.Load(), now)
}
var group WaitGroup
var v2 MutexValue[int]
var sum int
for i := range 10 {
group.Go(func() {
old1 := v2.Load()
old2 := v2.Swap(old1 + i)
delta := old2 - old1
v2.WithLock(func(p *int) { *p += delta })
})
sum += i
}
group.Wait()
if v2.Load() != sum {
t.Errorf("Load = %v, want %v", v2.Load(), sum)
}
}
func TestWaitGroupChan(t *testing.T) {
wg := NewWaitGroupChan()