1// Copyright 2009 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5// GOMAXPROCS=10 go test 6 7package sync_test 8 9import ( 10 "fmt" 11 "internal/testenv" 12 "os" 13 "os/exec" 14 "runtime" 15 "strings" 16 . "sync" 17 "testing" 18 "time" 19) 20 21func HammerSemaphore(s *uint32, loops int, cdone chan bool) { 22 for i := 0; i < loops; i++ { 23 Runtime_Semacquire(s) 24 Runtime_Semrelease(s, false, 0) 25 } 26 cdone <- true 27} 28 29func TestSemaphore(t *testing.T) { 30 s := new(uint32) 31 *s = 1 32 c := make(chan bool) 33 for i := 0; i < 10; i++ { 34 go HammerSemaphore(s, 1000, c) 35 } 36 for i := 0; i < 10; i++ { 37 <-c 38 } 39} 40 41func BenchmarkUncontendedSemaphore(b *testing.B) { 42 s := new(uint32) 43 *s = 1 44 HammerSemaphore(s, b.N, make(chan bool, 2)) 45} 46 47func BenchmarkContendedSemaphore(b *testing.B) { 48 b.StopTimer() 49 s := new(uint32) 50 *s = 1 51 c := make(chan bool) 52 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(2)) 53 b.StartTimer() 54 55 go HammerSemaphore(s, b.N/2, c) 56 go HammerSemaphore(s, b.N/2, c) 57 <-c 58 <-c 59} 60 61func HammerMutex(m *Mutex, loops int, cdone chan bool) { 62 for i := 0; i < loops; i++ { 63 if i%3 == 0 { 64 if m.TryLock() { 65 m.Unlock() 66 } 67 continue 68 } 69 m.Lock() 70 m.Unlock() 71 } 72 cdone <- true 73} 74 75func TestMutex(t *testing.T) { 76 if n := runtime.SetMutexProfileFraction(1); n != 0 { 77 t.Logf("got mutexrate %d expected 0", n) 78 } 79 defer runtime.SetMutexProfileFraction(0) 80 81 m := new(Mutex) 82 83 m.Lock() 84 if m.TryLock() { 85 t.Fatalf("TryLock succeeded with mutex locked") 86 } 87 m.Unlock() 88 if !m.TryLock() { 89 t.Fatalf("TryLock failed with mutex unlocked") 90 } 91 m.Unlock() 92 93 c := make(chan bool) 94 for i := 0; i < 10; i++ { 95 go HammerMutex(m, 1000, c) 96 } 97 for i := 0; i < 10; i++ { 98 <-c 99 } 100} 101 102var misuseTests = []struct { 103 name string 104 f func() 105}{ 106 { 107 "Mutex.Unlock", 108 func() { 109 var mu Mutex 110 mu.Unlock() 111 }, 112 }, 113 { 114 "Mutex.Unlock2", 115 func() { 116 var mu Mutex 117 mu.Lock() 118 mu.Unlock() 119 mu.Unlock() 120 }, 121 }, 122 { 123 "RWMutex.Unlock", 124 func() { 125 var mu RWMutex 126 mu.Unlock() 127 }, 128 }, 129 { 130 "RWMutex.Unlock2", 131 func() { 132 var mu RWMutex 133 mu.RLock() 134 mu.Unlock() 135 }, 136 }, 137 { 138 "RWMutex.Unlock3", 139 func() { 140 var mu RWMutex 141 mu.Lock() 142 mu.Unlock() 143 mu.Unlock() 144 }, 145 }, 146 { 147 "RWMutex.RUnlock", 148 func() { 149 var mu RWMutex 150 mu.RUnlock() 151 }, 152 }, 153 { 154 "RWMutex.RUnlock2", 155 func() { 156 var mu RWMutex 157 mu.Lock() 158 mu.RUnlock() 159 }, 160 }, 161 { 162 "RWMutex.RUnlock3", 163 func() { 164 var mu RWMutex 165 mu.RLock() 166 mu.RUnlock() 167 mu.RUnlock() 168 }, 169 }, 170} 171 172func init() { 173 if len(os.Args) == 3 && os.Args[1] == "TESTMISUSE" { 174 for _, test := range misuseTests { 175 if test.name == os.Args[2] { 176 func() { 177 defer func() { recover() }() 178 test.f() 179 }() 180 fmt.Printf("test completed\n") 181 os.Exit(0) 182 } 183 } 184 fmt.Printf("unknown test\n") 185 os.Exit(0) 186 } 187} 188 189func TestMutexMisuse(t *testing.T) { 190 testenv.MustHaveExec(t) 191 for _, test := range misuseTests { 192 out, err := exec.Command(os.Args[0], "TESTMISUSE", test.name).CombinedOutput() 193 if err == nil || !strings.Contains(string(out), "unlocked") { 194 t.Errorf("%s: did not find failure with message about unlocked lock: %s\n%s\n", test.name, err, out) 195 } 196 } 197} 198 199func TestMutexFairness(t *testing.T) { 200 var mu Mutex 201 stop := make(chan bool) 202 defer close(stop) 203 go func() { 204 for { 205 mu.Lock() 206 time.Sleep(100 * time.Microsecond) 207 mu.Unlock() 208 select { 209 case <-stop: 210 return 211 default: 212 } 213 } 214 }() 215 done := make(chan bool, 1) 216 go func() { 217 for i := 0; i < 10; i++ { 218 time.Sleep(100 * time.Microsecond) 219 mu.Lock() 220 mu.Unlock() 221 } 222 done <- true 223 }() 224 select { 225 case <-done: 226 case <-time.After(10 * time.Second): 227 t.Fatalf("can't acquire Mutex in 10 seconds") 228 } 229} 230 231func BenchmarkMutexUncontended(b *testing.B) { 232 type PaddedMutex struct { 233 Mutex 234 pad [128]uint8 235 } 236 b.RunParallel(func(pb *testing.PB) { 237 var mu PaddedMutex 238 for pb.Next() { 239 mu.Lock() 240 mu.Unlock() 241 } 242 }) 243} 244 245func benchmarkMutex(b *testing.B, slack, work bool) { 246 var mu Mutex 247 if slack { 248 b.SetParallelism(10) 249 } 250 b.RunParallel(func(pb *testing.PB) { 251 foo := 0 252 for pb.Next() { 253 mu.Lock() 254 mu.Unlock() 255 if work { 256 for i := 0; i < 100; i++ { 257 foo *= 2 258 foo /= 2 259 } 260 } 261 } 262 _ = foo 263 }) 264} 265 266func BenchmarkMutex(b *testing.B) { 267 benchmarkMutex(b, false, false) 268} 269 270func BenchmarkMutexSlack(b *testing.B) { 271 benchmarkMutex(b, true, false) 272} 273 274func BenchmarkMutexWork(b *testing.B) { 275 benchmarkMutex(b, false, true) 276} 277 278func BenchmarkMutexWorkSlack(b *testing.B) { 279 benchmarkMutex(b, true, true) 280} 281 282func BenchmarkMutexNoSpin(b *testing.B) { 283 // This benchmark models a situation where spinning in the mutex should be 284 // non-profitable and allows to confirm that spinning does not do harm. 285 // To achieve this we create excess of goroutines most of which do local work. 286 // These goroutines yield during local work, so that switching from 287 // a blocked goroutine to other goroutines is profitable. 288 // As a matter of fact, this benchmark still triggers some spinning in the mutex. 289 var m Mutex 290 var acc0, acc1 uint64 291 b.SetParallelism(4) 292 b.RunParallel(func(pb *testing.PB) { 293 c := make(chan bool) 294 var data [4 << 10]uint64 295 for i := 0; pb.Next(); i++ { 296 if i%4 == 0 { 297 m.Lock() 298 acc0 -= 100 299 acc1 += 100 300 m.Unlock() 301 } else { 302 for i := 0; i < len(data); i += 4 { 303 data[i]++ 304 } 305 // Elaborate way to say runtime.Gosched 306 // that does not put the goroutine onto global runq. 307 go func() { 308 c <- true 309 }() 310 <-c 311 } 312 } 313 }) 314} 315 316func BenchmarkMutexSpin(b *testing.B) { 317 // This benchmark models a situation where spinning in the mutex should be 318 // profitable. To achieve this we create a goroutine per-proc. 319 // These goroutines access considerable amount of local data so that 320 // unnecessary rescheduling is penalized by cache misses. 321 var m Mutex 322 var acc0, acc1 uint64 323 b.RunParallel(func(pb *testing.PB) { 324 var data [16 << 10]uint64 325 for i := 0; pb.Next(); i++ { 326 m.Lock() 327 acc0 -= 100 328 acc1 += 100 329 m.Unlock() 330 for i := 0; i < len(data); i += 4 { 331 data[i]++ 332 } 333 } 334 }) 335} 336