Go 并发编程-1.Mutex

基本定义

Mutex 实现了 Locker 接口:

1
2
3
4
type Locker interface {
Lock()
Unlock()
}

简单来说,互斥锁 Mutex 就提供两个方法 Lock 和 Unlock:进入临界区之前调用 Lock 方法,退出临界区的时候调用 Unlock 方法:

1
2
func(m *Mutex)Lock()
func(m *Mutex)Unlock()

不使用锁的例子,创建10个goroutine,每个goroutine执行10万次的加1操作,最后期望的结果是一百万。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

import (
"fmt"
"sync"
)

func main() {
var count = 0
// 使用WaitGroup等待10个goroutine完成
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
// 对变量count执行10次加1
for j := 0; j < 100000; j++ {
count++
}
}()
}
// 等待10个goroutine完成
wg.Wait()
fmt.Println(count)
}

由于 count++ 不是一个原子操作,且没有上锁,所以得出的结果值肯定小于一百万。

race detector

https://go.dev/blog/race-detector

在编译(compile)、测试(test)或者运行(run)Go 代码的时候,加上 -race 参数,就有可能发现并发问题。

-race 只能通过真正对实际地址进行读写访问的时候才能探测,所以它并不能在编译的时候发现 data race 的问题。而且只有在触发了 data race 之后,才能检测到,如果碰巧没有触发,则检测不到。-race 影响性能,不推荐在生产环境使用。

github 上还存在其他的 race detector 工具,例如: https://github.com/amit-davidson/Chronos

Mutex 用法

上面例子使用 Mutex 后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

package main


import (
"fmt"
"sync"
)


func main() {
// 互斥锁保护计数器
var mu sync.Mutex
// 计数器的值
var count = 0

// 辅助变量,用来确认所有的goroutine都完成
var wg sync.WaitGroup
wg.Add(10)

// 启动10个gourontine
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
// 累加10万次
for j := 0; j < 100000; j++ {
mu.Lock()
count++
mu.Unlock()
}
}()
}
wg.Wait()
fmt.Println(count)
}

或者采用嵌入字段的方式,将 Mutex 嵌入到 struct 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

func main() {
var counter Counter
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
for j := 0; j < 100000; j++ {
counter.Lock()
counter.Count++
counter.Unlock()
}
}()
}
wg.Wait()
fmt.Println(counter.Count)
}


type Counter struct {
sync.Mutex
Count uint64
}

或者把获取锁、释放锁、计数加一的逻辑封装成一个方法,对外不需要暴露锁等逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

func main() {
// 封装好的计数器
var counter Counter

var wg sync.WaitGroup
wg.Add(10)

// 启动10个goroutine
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
// 执行10万次累加
for j := 0; j < 100000; j++ {
counter.Incr() // 受到锁保护的方法
}
}()
}
wg.Wait()
fmt.Println(counter.Count())
}

// 线程安全的计数器类型
type Counter struct {
CounterType int
Name string

mu sync.Mutex
count uint64
}

// 加1的方法,内部使用互斥锁保护
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}

// 得到计数器的值,也需要锁保护
func (c *Counter) Count() uint64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}