| // Copyright 2017 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // GOMAXPROCS=10 go test |
| |
| // This is a copy of sync/rwmutex_test.go rewritten to test the |
| // runtime rwmutex. |
| |
| package runtime_test |
| |
| import ( |
| "fmt" |
| . "runtime" |
| "runtime/debug" |
| "sync/atomic" |
| "testing" |
| ) |
| |
| func parallelReader(m *RWMutex, clocked chan bool, cunlock *atomic.Bool, cdone chan bool) { |
| m.RLock() |
| clocked <- true |
| for !cunlock.Load() { |
| } |
| m.RUnlock() |
| cdone <- true |
| } |
| |
| func doTestParallelReaders(numReaders int) { |
| GOMAXPROCS(numReaders + 1) |
| var m RWMutex |
| m.Init() |
| clocked := make(chan bool, numReaders) |
| var cunlock atomic.Bool |
| cdone := make(chan bool) |
| for i := 0; i < numReaders; i++ { |
| go parallelReader(&m, clocked, &cunlock, cdone) |
| } |
| // Wait for all parallel RLock()s to succeed. |
| for i := 0; i < numReaders; i++ { |
| <-clocked |
| } |
| cunlock.Store(true) |
| // Wait for the goroutines to finish. |
| for i := 0; i < numReaders; i++ { |
| <-cdone |
| } |
| } |
| |
| func TestParallelRWMutexReaders(t *testing.T) { |
| if GOARCH == "wasm" { |
| t.Skip("wasm has no threads yet") |
| } |
| defer GOMAXPROCS(GOMAXPROCS(-1)) |
| // If runtime triggers a forced GC during this test then it will deadlock, |
| // since the goroutines can't be stopped/preempted. |
| // Disable GC for this test (see issue #10958). |
| defer debug.SetGCPercent(debug.SetGCPercent(-1)) |
| // SetGCPercent waits until the mark phase is over, but the runtime |
| // also preempts at the start of the sweep phase, so make sure that's |
| // done too. |
| GC() |
| |
| doTestParallelReaders(1) |
| doTestParallelReaders(3) |
| doTestParallelReaders(4) |
| } |
| |
| func reader(rwm *RWMutex, num_iterations int, activity *int32, cdone chan bool) { |
| for i := 0; i < num_iterations; i++ { |
| rwm.RLock() |
| n := atomic.AddInt32(activity, 1) |
| if n < 1 || n >= 10000 { |
| panic(fmt.Sprintf("wlock(%d)\n", n)) |
| } |
| for i := 0; i < 100; i++ { |
| } |
| atomic.AddInt32(activity, -1) |
| rwm.RUnlock() |
| } |
| cdone <- true |
| } |
| |
| func writer(rwm *RWMutex, num_iterations int, activity *int32, cdone chan bool) { |
| for i := 0; i < num_iterations; i++ { |
| rwm.Lock() |
| n := atomic.AddInt32(activity, 10000) |
| if n != 10000 { |
| panic(fmt.Sprintf("wlock(%d)\n", n)) |
| } |
| for i := 0; i < 100; i++ { |
| } |
| atomic.AddInt32(activity, -10000) |
| rwm.Unlock() |
| } |
| cdone <- true |
| } |
| |
| func HammerRWMutex(gomaxprocs, numReaders, num_iterations int) { |
| GOMAXPROCS(gomaxprocs) |
| // Number of active readers + 10000 * number of active writers. |
| var activity int32 |
| var rwm RWMutex |
| rwm.Init() |
| cdone := make(chan bool) |
| go writer(&rwm, num_iterations, &activity, cdone) |
| var i int |
| for i = 0; i < numReaders/2; i++ { |
| go reader(&rwm, num_iterations, &activity, cdone) |
| } |
| go writer(&rwm, num_iterations, &activity, cdone) |
| for ; i < numReaders; i++ { |
| go reader(&rwm, num_iterations, &activity, cdone) |
| } |
| // Wait for the 2 writers and all readers to finish. |
| for i := 0; i < 2+numReaders; i++ { |
| <-cdone |
| } |
| } |
| |
| func TestRWMutex(t *testing.T) { |
| defer GOMAXPROCS(GOMAXPROCS(-1)) |
| n := 1000 |
| if testing.Short() { |
| n = 5 |
| } |
| HammerRWMutex(1, 1, n) |
| HammerRWMutex(1, 3, n) |
| HammerRWMutex(1, 10, n) |
| HammerRWMutex(4, 1, n) |
| HammerRWMutex(4, 3, n) |
| HammerRWMutex(4, 10, n) |
| HammerRWMutex(10, 1, n) |
| HammerRWMutex(10, 3, n) |
| HammerRWMutex(10, 10, n) |
| HammerRWMutex(10, 5, n) |
| } |
| |
| func BenchmarkRWMutexUncontended(b *testing.B) { |
| type PaddedRWMutex struct { |
| RWMutex |
| pad [32]uint32 |
| } |
| b.RunParallel(func(pb *testing.PB) { |
| var rwm PaddedRWMutex |
| rwm.Init() |
| for pb.Next() { |
| rwm.RLock() |
| rwm.RLock() |
| rwm.RUnlock() |
| rwm.RUnlock() |
| rwm.Lock() |
| rwm.Unlock() |
| } |
| }) |
| } |
| |
| func benchmarkRWMutex(b *testing.B, localWork, writeRatio int) { |
| var rwm RWMutex |
| rwm.Init() |
| b.RunParallel(func(pb *testing.PB) { |
| foo := 0 |
| for pb.Next() { |
| foo++ |
| if foo%writeRatio == 0 { |
| rwm.Lock() |
| rwm.Unlock() |
| } else { |
| rwm.RLock() |
| for i := 0; i != localWork; i += 1 { |
| foo *= 2 |
| foo /= 2 |
| } |
| rwm.RUnlock() |
| } |
| } |
| _ = foo |
| }) |
| } |
| |
| func BenchmarkRWMutexWrite100(b *testing.B) { |
| benchmarkRWMutex(b, 0, 100) |
| } |
| |
| func BenchmarkRWMutexWrite10(b *testing.B) { |
| benchmarkRWMutex(b, 0, 10) |
| } |
| |
| func BenchmarkRWMutexWorkWrite100(b *testing.B) { |
| benchmarkRWMutex(b, 100, 100) |
| } |
| |
| func BenchmarkRWMutexWorkWrite10(b *testing.B) { |
| benchmarkRWMutex(b, 100, 10) |
| } |