Golang 官方的 sync 的包里面有很多用于并发的类型,包括但不限于
- sync.Map
- sync.Mutex
- sync.Once
- sync.Pool
使用这些类型的时候有一个要注意的点,就是我们不能传递值本身,否则就起不到应用的作用
func main() {
counter := NewCounter1()
for i := 0; i < 100; i += 1{
go func() {
counter.Increment1("foo")
}()
}
for i := 0; i < 100; i += 1{
go func() {
counter.Increment1("bar")
}()
}
time.Sleep(2 * time.Second)
fmt.Println(counter.counters)
}
type Counter1 struct {
mu sync.Mutex
counters map[string]int
}
func NewCounter1() Counter1 {
return Counter1{
counters: map[string]int{},
mu: sync.Mutex{},
}
}
func (c Counter1) Increment1(name string) {
c.mu.Lock()
defer c.mu.Unlock()
c.counters[name]++
}
首先看一下上面的例子,有一个 struct Counter1,里面包含两个元素,一个是互斥量,另一个是 map,这个 struct 有一个增加计数的方法,并且在调用的时候会先上锁,理论上我们可以并发调用,因为有互斥锁来保证同步,但实际情况真是这样吗,如果我们执行上面的代码就会发现代码会输出
fatal error: concurrent map writes
那为什么会这样呢?因为结构体里面的互斥量是值本身,那么并发调用的时候就不是指向同一个互斥量所以也就起不到同步的作用
func (c Counter2) Increment2(name string) {
c.mu.Lock()
defer c.mu.Unlock()
c.counters[name]++
}
type Counter2 struct {
mu *sync.Mutex
counters map[string]int
}
func NewCounter2() Counter2 {
return Counter2{
mu: &sync.Mutex{},
counters: map[string]int{},
}
}
如上所示,只要将结构体里面的变量换成指针类型就可以,这样指向的就是同一个互斥量。 所以在使用 sync 包里变量的时候,一定要注意使用指针类型而不是值本身