geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
166 stars 21 forks source link

读写锁和互斥锁的性能比较 | Go 语言高性能编程 | 极客兔兔 #113

Open geektutu opened 3 years ago

geektutu commented 3 years ago

https://geektutu.com/post/hpg-mutex.html

Go 语言/golang 高性能编程,Go 语言进阶教程,Go 语言高性能编程(high performance go)。介绍了读写锁(sync.RWMutex)和互斥锁(sync.Mutex)在不同的读写比情况下的性能开销。

GanZhiXiong commented 3 years ago

你能看下下面的代码,为什么使用读写锁比互斥锁执行的时间要长2倍? 这两个测试用例,代码唯一的区别就是var mutex sync.Mutexvar mutex sync.RWMutex

// 使用互斥锁
// 耗时大概3秒
func TestSharedVariablesConcurrentlyTest2(t *testing.T) {
    var amount int
    var mutex sync.Mutex

    defer func(t time.Time) {
        fmt.Println(float64(time.Now().UnixNano() - t.UnixNano()) / 1e9)
    }(time.Now())

    waiting := make(chan struct{})
    //mutex = &sync.Mutex{}

    go func(){
        for i:=0; i < 100000000; i++{
            //t.Log("a")
            mutex.Lock()
            amount++
            mutex.Unlock()
        }
        waiting <- struct{}{}
    }()

    for i:=0; i < 100000000; i++{
        //t.Log("b")
        mutex.Lock()
        amount--
        mutex.Unlock()
    }

    <-waiting
    fmt.Println(amount)     // 最后查看余额
}

// 使用读写锁
// 耗时大概6秒
func TestSharedVariablesConcurrentlyTest3(t *testing.T) {
    var amount int
    var mutex sync.RWMutex

    defer func(t time.Time) {
        fmt.Println(float64(time.Now().UnixNano() - t.UnixNano()) / 1e9)
    }(time.Now())

    waiting := make(chan struct{})
    //mutex = sync.RWMutex{}

    go func(){
        for i:=0; i < 100000000; i++{
            //t.Log("a")
            mutex.Lock()
            amount++
            mutex.Unlock()
        }
        waiting <- struct{}{}
    }()

    for i:=0; i < 100000000; i++{
        //t.Log("b")
        mutex.Lock()
        amount--
        mutex.Unlock()
    }

    <-waiting
    fmt.Println(amount)     // 最后查看余额
}
fufay commented 2 years ago

@GanZhiXiong 你能看下下面的代码,为什么使用读写锁比互斥锁执行的时间要长2倍? 这两个测试用例,代码唯一的区别就是var mutex sync.Mutexvar mutex sync.RWMutex

// 使用互斥锁
// 耗时大概3秒
func TestSharedVariablesConcurrentlyTest2(t *testing.T) {
  var amount int
  var mutex sync.Mutex

  defer func(t time.Time) {
      fmt.Println(float64(time.Now().UnixNano() - t.UnixNano()) / 1e9)
  }(time.Now())

  waiting := make(chan struct{})
  //mutex = &sync.Mutex{}

  go func(){
      for i:=0; i < 100000000; i++{
          //t.Log("a")
          mutex.Lock()
          amount++
          mutex.Unlock()
      }
      waiting <- struct{}{}
  }()

  for i:=0; i < 100000000; i++{
      //t.Log("b")
      mutex.Lock()
      amount--
      mutex.Unlock()
  }

  <-waiting
  fmt.Println(amount)     // 最后查看余额
}

// 使用读写锁
// 耗时大概6秒
func TestSharedVariablesConcurrentlyTest3(t *testing.T) {
  var amount int
  var mutex sync.RWMutex

  defer func(t time.Time) {
      fmt.Println(float64(time.Now().UnixNano() - t.UnixNano()) / 1e9)
  }(time.Now())

  waiting := make(chan struct{})
  //mutex = sync.RWMutex{}

  go func(){
      for i:=0; i < 100000000; i++{
          //t.Log("a")
          mutex.Lock()
          amount++
          mutex.Unlock()
      }
      waiting <- struct{}{}
  }()

  for i:=0; i < 100000000; i++{
      //t.Log("b")
      mutex.Lock()
      amount--
      mutex.Unlock()
  }

  <-waiting
  fmt.Println(amount)     // 最后查看余额
}

兄弟,你这个问题较多哦。。 1、读写锁读写锁,读写分离,你amount++/amout--都是写,还测试啥哦。 2、读写锁的读加锁解锁用的是 RLock()和RUnlock(),不是Lock()和Unlock() 3、实际的读写操作都是有操作延时的,不可能像测试代码里只做个++或者--,或者是:_ = amout。所以应该用time.sleep()模拟延时,比如1微秒。 4、读写锁的建立实际上也是要消耗cpu时间的,这个开销比互斥锁稍微多一点点。 5、读写锁面对的情况实际上是读要远大于写,比如写100次,读要几千次。

stuchilde commented 2 years ago

我本地测试下来同样的代码跟你的结果差异很大,这是为啥呀?是不是因为golang的版本不一样导致的呀? 实验结果如下: /usr/local/Cellar/go/1.16.2/libexec/bin/go test -v ./... -bench . -run ^$ goos: darwin goarch: amd64 pkg: example cpu: Intel(R) Core(TM) i5-5257U CPU @ 2.70GHz BenchmarkReadMore BenchmarkReadMore-4 136 8415824 ns/op BenchmarkReadMoreRW BenchmarkReadMoreRW-4 2640 457446 ns/op BenchmarkWriteMore BenchmarkWriteMore-4 140 8112753 ns/op BenchmarkWriteMoreRW BenchmarkWriteMoreRW-4 2245 473042 ns/op BenchmarkEqual BenchmarkEqual-4 147 8295812 ns/op BenchmarkEqualRW BenchmarkEqualRW-4 2433 446097 ns/op PASS ok example 10.558s

Process finished with the exit code 0

haima96 commented 2 years ago

同样的代码,为什么在我电脑上运行了将近70秒。。。都快测不出两种锁的差距来了 I7-8700 12核看比貌似比文中的8核快吧,楼上的I5-5257U为啥也比我快 操作系统的差异吗?

xyc0123456789 commented 2 years ago

@haima96 同样的代码,为什么在我电脑上运行了将近70秒。。。都快测不出两种锁的差距来了 I7-8700 12核看比貌似比文中的8核快吧,楼上的I5-5257U为啥也比我快 操作系统的差异吗?

go version go1.17.6 贴两个我在本地windows下和本地虚拟机centos7下情况,应该是操作系统的差异导致的问题 ''' goos: windows goarch: amd64 pkg: wp20220225 cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz BenchmarkReadMore BenchmarkReadMore-12 1 1738722700 ns/op BenchmarkReadMoreRW BenchmarkReadMoreRW-12 6 178910700 ns/op BenchmarkWriteMore BenchmarkWriteMore-12 1 1724833800 ns/op BenchmarkWriteMoreRW BenchmarkWriteMoreRW-12 1 1566875800 ns/op BenchmarkEqual BenchmarkEqual-12 1 1719672300 ns/op BenchmarkEqualRW BenchmarkEqualRW-12 2 886586600 ns/op PASS ''' ''' goos: linux goarch: amd64 pkg: wp20220225 cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz BenchmarkReadMore-12 45 31306217 ns/op BenchmarkReadMoreRW-12 322 13520457 ns/op BenchmarkWriteMore-12 25 119642091 ns/op BenchmarkWriteMoreRW-12 33 64905106 ns/op BenchmarkEqual-12 42 92607716 ns/op BenchmarkEqualRW-12 80 47145911 ns/op PASS ok wp20220225 21.181s '''