Black Lives Matter. Support the Equal Justice Initiative.

Source file src/sync/mutex_test.go

Documentation: sync

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

View as plain text