|  | // Copyright 2009 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 | 
|  |  | 
|  | package sync_test | 
|  |  | 
|  | import ( | 
|  | "fmt" | 
|  | "internal/testenv" | 
|  | "os" | 
|  | "os/exec" | 
|  | "runtime" | 
|  | "strings" | 
|  | . "sync" | 
|  | "testing" | 
|  | "time" | 
|  | ) | 
|  |  | 
|  | func HammerSemaphore(s *uint32, loops int, cdone chan bool) { | 
|  | for i := 0; i < loops; i++ { | 
|  | Runtime_Semacquire(s) | 
|  | Runtime_Semrelease(s, false, 0) | 
|  | } | 
|  | cdone <- true | 
|  | } | 
|  |  | 
|  | func TestSemaphore(t *testing.T) { | 
|  | s := new(uint32) | 
|  | *s = 1 | 
|  | c := make(chan bool) | 
|  | for i := 0; i < 10; i++ { | 
|  | go HammerSemaphore(s, 1000, c) | 
|  | } | 
|  | for i := 0; i < 10; i++ { | 
|  | <-c | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkUncontendedSemaphore(b *testing.B) { | 
|  | s := new(uint32) | 
|  | *s = 1 | 
|  | HammerSemaphore(s, b.N, make(chan bool, 2)) | 
|  | } | 
|  |  | 
|  | func BenchmarkContendedSemaphore(b *testing.B) { | 
|  | b.StopTimer() | 
|  | s := new(uint32) | 
|  | *s = 1 | 
|  | c := make(chan bool) | 
|  | defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2)) | 
|  | b.StartTimer() | 
|  |  | 
|  | go HammerSemaphore(s, b.N/2, c) | 
|  | go HammerSemaphore(s, b.N/2, c) | 
|  | <-c | 
|  | <-c | 
|  | } | 
|  |  | 
|  | func HammerMutex(m *Mutex, loops int, cdone chan bool) { | 
|  | for i := 0; i < loops; i++ { | 
|  | m.Lock() | 
|  | m.Unlock() | 
|  | } | 
|  | cdone <- true | 
|  | } | 
|  |  | 
|  | func TestMutex(t *testing.T) { | 
|  | if n := runtime.SetMutexProfileFraction(1); n != 0 { | 
|  | t.Logf("got mutexrate %d expected 0", n) | 
|  | } | 
|  | defer runtime.SetMutexProfileFraction(0) | 
|  | m := new(Mutex) | 
|  | c := make(chan bool) | 
|  | for i := 0; i < 10; i++ { | 
|  | go HammerMutex(m, 1000, c) | 
|  | } | 
|  | for i := 0; i < 10; i++ { | 
|  | <-c | 
|  | } | 
|  | } | 
|  |  | 
|  | var misuseTests = []struct { | 
|  | name string | 
|  | f    func() | 
|  | }{ | 
|  | { | 
|  | "Mutex.Unlock", | 
|  | func() { | 
|  | var mu Mutex | 
|  | mu.Unlock() | 
|  | }, | 
|  | }, | 
|  | { | 
|  | "Mutex.Unlock2", | 
|  | func() { | 
|  | var mu Mutex | 
|  | mu.Lock() | 
|  | mu.Unlock() | 
|  | mu.Unlock() | 
|  | }, | 
|  | }, | 
|  | { | 
|  | "RWMutex.Unlock", | 
|  | func() { | 
|  | var mu RWMutex | 
|  | mu.Unlock() | 
|  | }, | 
|  | }, | 
|  | { | 
|  | "RWMutex.Unlock2", | 
|  | func() { | 
|  | var mu RWMutex | 
|  | mu.RLock() | 
|  | mu.Unlock() | 
|  | }, | 
|  | }, | 
|  | { | 
|  | "RWMutex.Unlock3", | 
|  | func() { | 
|  | var mu RWMutex | 
|  | mu.Lock() | 
|  | mu.Unlock() | 
|  | mu.Unlock() | 
|  | }, | 
|  | }, | 
|  | { | 
|  | "RWMutex.RUnlock", | 
|  | func() { | 
|  | var mu RWMutex | 
|  | mu.RUnlock() | 
|  | }, | 
|  | }, | 
|  | { | 
|  | "RWMutex.RUnlock2", | 
|  | func() { | 
|  | var mu RWMutex | 
|  | mu.Lock() | 
|  | mu.RUnlock() | 
|  | }, | 
|  | }, | 
|  | { | 
|  | "RWMutex.RUnlock3", | 
|  | func() { | 
|  | var mu RWMutex | 
|  | mu.RLock() | 
|  | mu.RUnlock() | 
|  | mu.RUnlock() | 
|  | }, | 
|  | }, | 
|  | } | 
|  |  | 
|  | func init() { | 
|  | if len(os.Args) == 3 && os.Args[1] == "TESTMISUSE" { | 
|  | for _, test := range misuseTests { | 
|  | if test.name == os.Args[2] { | 
|  | func() { | 
|  | defer func() { recover() }() | 
|  | test.f() | 
|  | }() | 
|  | fmt.Printf("test completed\n") | 
|  | os.Exit(0) | 
|  | } | 
|  | } | 
|  | fmt.Printf("unknown test\n") | 
|  | os.Exit(0) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMutexMisuse(t *testing.T) { | 
|  | testenv.MustHaveExec(t) | 
|  | for _, test := range misuseTests { | 
|  | out, err := exec.Command(os.Args[0], "TESTMISUSE", test.name).CombinedOutput() | 
|  | if err == nil || !strings.Contains(string(out), "unlocked") { | 
|  | t.Errorf("%s: did not find failure with message about unlocked lock: %s\n%s\n", test.name, err, out) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestMutexFairness(t *testing.T) { | 
|  | var mu Mutex | 
|  | stop := make(chan bool) | 
|  | defer close(stop) | 
|  | go func() { | 
|  | for { | 
|  | mu.Lock() | 
|  | time.Sleep(100 * time.Microsecond) | 
|  | mu.Unlock() | 
|  | select { | 
|  | case <-stop: | 
|  | return | 
|  | default: | 
|  | } | 
|  | } | 
|  | }() | 
|  | done := make(chan bool, 1) | 
|  | go func() { | 
|  | for i := 0; i < 10; i++ { | 
|  | time.Sleep(100 * time.Microsecond) | 
|  | mu.Lock() | 
|  | mu.Unlock() | 
|  | } | 
|  | done <- true | 
|  | }() | 
|  | select { | 
|  | case <-done: | 
|  | case <-time.After(10 * time.Second): | 
|  | t.Fatalf("can't acquire Mutex in 10 seconds") | 
|  | } | 
|  | } | 
|  |  | 
|  | func BenchmarkMutexUncontended(b *testing.B) { | 
|  | type PaddedMutex struct { | 
|  | Mutex | 
|  | pad [128]uint8 | 
|  | } | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | var mu PaddedMutex | 
|  | for pb.Next() { | 
|  | mu.Lock() | 
|  | mu.Unlock() | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func benchmarkMutex(b *testing.B, slack, work bool) { | 
|  | var mu Mutex | 
|  | if slack { | 
|  | b.SetParallelism(10) | 
|  | } | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | foo := 0 | 
|  | for pb.Next() { | 
|  | mu.Lock() | 
|  | mu.Unlock() | 
|  | if work { | 
|  | for i := 0; i < 100; i++ { | 
|  | foo *= 2 | 
|  | foo /= 2 | 
|  | } | 
|  | } | 
|  | } | 
|  | _ = foo | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkMutex(b *testing.B) { | 
|  | benchmarkMutex(b, false, false) | 
|  | } | 
|  |  | 
|  | func BenchmarkMutexSlack(b *testing.B) { | 
|  | benchmarkMutex(b, true, false) | 
|  | } | 
|  |  | 
|  | func BenchmarkMutexWork(b *testing.B) { | 
|  | benchmarkMutex(b, false, true) | 
|  | } | 
|  |  | 
|  | func BenchmarkMutexWorkSlack(b *testing.B) { | 
|  | benchmarkMutex(b, true, true) | 
|  | } | 
|  |  | 
|  | func BenchmarkMutexNoSpin(b *testing.B) { | 
|  | // This benchmark models a situation where spinning in the mutex should be | 
|  | // non-profitable and allows to confirm that spinning does not do harm. | 
|  | // To achieve this we create excess of goroutines most of which do local work. | 
|  | // These goroutines yield during local work, so that switching from | 
|  | // a blocked goroutine to other goroutines is profitable. | 
|  | // As a matter of fact, this benchmark still triggers some spinning in the mutex. | 
|  | var m Mutex | 
|  | var acc0, acc1 uint64 | 
|  | b.SetParallelism(4) | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | c := make(chan bool) | 
|  | var data [4 << 10]uint64 | 
|  | for i := 0; pb.Next(); i++ { | 
|  | if i%4 == 0 { | 
|  | m.Lock() | 
|  | acc0 -= 100 | 
|  | acc1 += 100 | 
|  | m.Unlock() | 
|  | } else { | 
|  | for i := 0; i < len(data); i += 4 { | 
|  | data[i]++ | 
|  | } | 
|  | // Elaborate way to say runtime.Gosched | 
|  | // that does not put the goroutine onto global runq. | 
|  | go func() { | 
|  | c <- true | 
|  | }() | 
|  | <-c | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | func BenchmarkMutexSpin(b *testing.B) { | 
|  | // This benchmark models a situation where spinning in the mutex should be | 
|  | // profitable. To achieve this we create a goroutine per-proc. | 
|  | // These goroutines access considerable amount of local data so that | 
|  | // unnecessary rescheduling is penalized by cache misses. | 
|  | var m Mutex | 
|  | var acc0, acc1 uint64 | 
|  | b.RunParallel(func(pb *testing.PB) { | 
|  | var data [16 << 10]uint64 | 
|  | for i := 0; pb.Next(); i++ { | 
|  | m.Lock() | 
|  | acc0 -= 100 | 
|  | acc1 += 100 | 
|  | m.Unlock() | 
|  | for i := 0; i < len(data); i += 4 { | 
|  | data[i]++ | 
|  | } | 
|  | } | 
|  | }) | 
|  | } |