golang 并发程序写入map两种实现方式sync.Mutex和chan的效率对比

golang原生的数据结构map,由于是通过hash方式实现的,不支持并发写入,但是在golang很多并发场景中,不可避免的需要写入map,下面介绍两种解决map并发写入的实现方式:

  1. sync.Mutex互斥锁(通过加锁解锁解决map不能并发写入的问题)
  2. chan (通过管道来解决map并发的问题),chan的存在完美解决goroutine之间的通信以及数据race问题,但是它的性能如何呢,下面让我们来测一下

首先先上代码:

package main

import (
"testing"
"sync"
)

func BenchmarkTest(b *testing.B) {
var m = make(map[int]int, 10000)
var mu = &sync.Mutex{}
var c = make(chan int, 200)
var w = &sync.WaitGroup{}

w.Add(b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
c <- 1
go func(index int) {
mu.Lock()
m[index] = 0
mu.Unlock()
w.Done()
<- c
}(i)
}
w.Wait()
}

func BenchmarkTestBlock(b *testing.B) {
var m = make(map[int]int, 10000)
var c = make(chan int, 200)
var dataChan = make(chan int, 200)
var w = &sync.WaitGroup{}

w.Add(b.N+1)
go func() {
for i := 0; i < b.N; i++ {
m[<- dataChan] = 0
}
w.Done()
}()

b.ResetTimer()
for i := 0; i < b.N; i++ {
c<-1
go func(index int) {
dataChan <- index
w.Done()
<-c
}(i)
}
w.Wait()
}

代码很简洁,我就不多解释了。

看一下执行效率    

 

可以看到,chan的实现方式其性能并不理想,比sync.Mutex的实现方式的效率低了大约25%左右。 

chan的效率为什么不理想呢,其实原因很简单,下面看一下它的数据结构就一目了然了

package main

import (
    "fmt"
)

func main() {
    s := "hello, world!"
    b := []byte(s)
    c := make(chan int, 1)
    c<-1
    fmt.Println(s, b, <-c)
}

 

我们可以看到channel的实现是通过互斥锁和数组的方式实现的,而且还有一些其他字段的同步,因此它的效率是不会比直接用互斥锁更快的。

很多看上去很高端很简洁的东西,其实是编辑器和runtime在后面为我们码农实现,当然与之相来是额外的内存和时间开销。

 

posted @ 2018-09-29 11:36  水月江南  阅读(3670)  评论(1编辑  收藏  举报