1
+ 首发于:https://studygolang.com/articles/35385
1
2
2
- ![ ] ( https://gitee.com/double12gzh/wiki-pictures/raw/master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/%E5%9B%BE0.png )
3
+ # 如何使用 atomic 包减少锁冲突
3
4
4
- # Go如何减少atomic包的锁冲突
5
+ ![ ] ( https://raw.githubusercontent.com/studygolang/gctt-images2/master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/1.png )
5
6
6
7
## 写在前面
7
8
8
- > 本文基于GoLang 1.14
9
+ > 本文基于 Golang 1.14
9
10
10
- Go提供了channel或mutex等内存同步机制 ,有助于解决不同的问题。在共享内存的情况下,mutex可以保护内存不发生数据竞争 (data race)。不过,虽然存在两个mutex,但Go也通过 ` atomic ` 包提供了原子内存基元来提高性能。在深入研究解决方案之前,我们先回过头来看看数据竞争。
11
+ Go 提供了 channel 或 mutex 等内存同步机制 ,有助于解决不同的问题。在共享内存的情况下,mutex 可以保护内存不发生数据竞争 (data race)。不过,虽然存在两个 mutex,但 Go 也通过 ` atomic ` 包提供了原子内存基元来提高性能。在深入研究解决方案之前,我们先回过头来看看数据竞争。
11
12
12
13
## 数据竞争
13
14
14
- 当两个或两个以上的goroutine同时访问同一块内存区域 ,并且其中至少有一个在写时,就会发生数据竞争。虽然` map ` 有内部有一定的机制来防止数据竞争 ,但一个简单的结构体并没有任何的机制,因此容易发生数据竞争。
15
+ 当两个或两个以上的 goroutine 同时访问同一块内存区域 ,并且其中至少有一个在写时,就会发生数据竞争。虽然 ` map ` 内部有一定的机制来防止数据竞争 ,但一个简单的结构体并没有任何的机制,因此容易发生数据竞争。
15
16
16
- 为了说明数据竞争,我以一个** goroutine持续更新的配置 ** 为例向大家展示一下。
17
+ 为了说明数据竞争,我以一个** goroutine 持续更新的配置 ** 为例向大家展示一下。
17
18
18
- ``` golang
19
+ ``` go
19
20
package main
20
21
21
22
import (
@@ -30,7 +31,7 @@ type Config struct {
30
31
func main () {
31
32
cfg := &Config{}
32
33
33
- // 启动一个writer goroutine,不断写入数据
34
+ // 启动一个 writer goroutine,不断写入数据
34
35
go func () {
35
36
i := 0
36
37
@@ -40,7 +41,7 @@ func main() {
40
41
}
41
42
}()
42
43
43
- // 启动多个reader goroutine,不断获取数据
44
+ // 启动多个 reader goroutine,不断获取数据
44
45
var wg sync.WaitGroup
45
46
for n := 0 ; n < 4 ; n++ {
46
47
wg.Add (1 )
@@ -66,7 +67,7 @@ F:\hello>go run main.go
66
67
& main.Config{a:[]int{181607, 181617, 181624, 181631, 181636, 181643}}
67
68
```
68
69
69
- 我们可以在运行时加入参数` --race ` 看一下结果:
70
+ 我们可以在运行时加入参数 ` --race ` 看一下结果:
70
71
71
72
``` bash
72
73
@@ -77,7 +78,7 @@ F:\hello>go run --race main.go
77
78
& main.Config{a:[]int(nil)}
78
79
WARNING: DATA RACE& main.Config{a:[]int(nil)}
79
80
80
- Read at 0x00c00000c210 by goroutine 9:
81
+ Read at 0x00c00000c210 by Goroutine 9:
81
82
reflect.Value.Int ()
82
83
D:/Go/src/reflect/value.go:988 +0x3584
83
84
fmt.(*pp).printValue ()
@@ -103,27 +104,27 @@ Previous write at 0x00c00000c210 by goroutine 7:
103
104
main.main.func1 ()
104
105
F:/hello/main.go:21 +0x66
105
106
106
- Goroutine 9 (running) created at:
107
+ goroutine 9 (running) created at:
107
108
main.main ()
108
109
F:/hello/main.go:29 +0x124
109
110
110
- Goroutine 7 (running) created at:
111
+ goroutine 7 (running) created at:
111
112
main.main ()
112
113
F:/hello/main.go:16 +0x95
113
114
==================
114
115
```
115
116
116
- 为了避免同时读写过程中产生的数据竞争最常采用的方法可能是使用` mutex ` 或 ` atomic ` 包。
117
+ 为了避免同时读写过程中产生的数据竞争最常采用的方法可能是使用 ` mutex ` 或 ` atomic ` 包。
117
118
118
- ## Mutex?还是Atomic ?
119
+ ## Mutex?还是 Atomic ?
119
120
120
- 标准库为 ` sync ` 包提供了两种 ` mutex ` :** sync.Mutex** 和 ** sync.RWMutex** 。后者在你的程序需要处理多个读操作和极少的写操作时进行了优化。
121
+ 标准库在 ` sync ` 包提供了两种互斥锁 :** sync.Mutex** 和 ** sync.RWMutex** 。后者在你的程序需要处理多个读操作和极少的写操作时进行了优化。
121
122
122
123
针对上面代码中产生的数据竞争问题,我们看一下,如何解决呢?
123
124
124
- ### 使用` sync.Mutex ` 解决数据竞争
125
+ ### 使用 ` sync.Mutex ` 解决数据竞争
125
126
126
- ``` golang
127
+ ``` go
127
128
package main
128
129
129
130
import (
@@ -140,7 +141,7 @@ func main() {
140
141
cfg := &Config{}
141
142
var mux sync.RWMutex
142
143
143
- // 启动一个writer goroutine,不断写入数据
144
+ // 启动一个 writer goroutine,不断写入数据
144
145
go func () {
145
146
i := 0
146
147
@@ -153,7 +154,7 @@ func main() {
153
154
}
154
155
}()
155
156
156
- // 启动多个reader goroutine,不断获取数据
157
+ // 启动多个 reader goroutine,不断获取数据
157
158
var wg sync.WaitGroup
158
159
for n := 0 ; n < 4 ; n++ {
159
160
wg.Add (1 )
@@ -172,7 +173,7 @@ func main() {
172
173
}
173
174
```
174
175
175
- 通过上面的代码,我们做了两处改动。第一处改动在写数据前通过` mux.Lock() ` 加了一把锁;第二处改动在读数据前通过` mux.RLock() ` 加了一把读锁。
176
+ 通过上面的代码,我们做了两处改动。第一处改动在写数据前通过 ` mux.Lock() ` 加了一把锁;第二处改动在读数据前通过 ` mux.RLock() ` 加了一把读锁。
176
177
177
178
运行上述代码看一下结果:
178
179
@@ -190,9 +191,9 @@ F:\hello>go run --race main.go
190
191
191
192
这次达到了我们的预期并且也没有产生数据竞争。
192
193
193
- ### 使用` atomic ` 解决数据竞争
194
+ ### 使用 ` atomic ` 解决数据竞争
194
195
195
- ``` golang
196
+ ``` go
196
197
package main
197
198
198
199
import (
@@ -237,7 +238,7 @@ func main() {
237
238
}
238
239
```
239
240
240
- 这里我们使用了` atomic ` 包,通过运行我们发现,也同样达到了我们期望的结果:
241
+ 这里我们使用了 ` atomic ` 包,通过运行我们发现,也同样达到了我们期望的结果:
241
242
242
243
``` bash
243
244
[...]
@@ -247,17 +248,16 @@ main.Config{a:[]int{219826, 219827, 219828, 219829, 219830, 219831}}
247
248
main.Config{a:[]int{219948, 219949, 219950, 219951, 219952, 219953}}
248
249
```
249
250
250
- 从生成的输出结果而言,看起来使用` atomic ` 包的解决方案要快得多,因为它可以生成更高的数字序列。
251
+ 从生成的输出结果而言,看起来使用 ` atomic ` 包的解决方案要快得多,因为它可以生成更高的数字序列。
251
252
为了更加严谨的证明这个结果,我们下面将对这两个程序进行基准测试。
252
253
253
254
## 性能分析
254
255
255
- 一个benchmark应该根据被测量的内容来解释 。因此,我们假设之前的程序,有一个不断存储新配置的` 数据写入器 ` ,同时也有多个不断读取配置的` 数据读取器 ` 。为了涵盖更多潜在的场景,我们还将包括一个只有` 数据读取器 ` 的benchmark,假设Config不经常改变 。
256
+ 一个 benchmark 应该根据被测量的内容来解释 。因此,我们假设之前的程序,有一个不断存储新配置的 ` 数据写入器 ` ,同时也有多个不断读取配置的 ` 数据读取器 ` 。为了涵盖更多潜在的场景,我们还将包括一个只有 ` 数据读取器 ` 的 benchmark,假设 Config 不经常改变 。
256
257
257
- 下面是部分benchmark的代码:
258
-
259
- ``` golang
258
+ 下面是部分 benchmark 的代码:
260
259
260
+ ``` go
261
261
func BenchmarkMutexMultipleReaders (b *testing .B ) {
262
262
var lastValue uint64
263
263
var mux sync.RWMutex
@@ -295,35 +295,36 @@ MutexOneWriterMultipleReaders-4 717ns ± 3%
295
295
MutexMultipleReaders-4 176ns ± 2%
296
296
```
297
297
298
- 基准测试证实了我们之前看到的性能情况。为了了解mutex的瓶颈到底在哪里 ,我们可以在启用` tracer ` 的情况下重新运行程序。
298
+ 基准测试证实了我们之前看到的性能情况。为了了解 mutex 的瓶颈到底在哪里 ,我们可以在启用 ` tracer ` 的情况下重新运行程序。
299
299
300
- > 更多关于` tracer ` 的内容,请参考[ trace] ( https://medium.com/a-journey-with-go/go-discovery-of-the-trace-package-e5a821743c3c ) 这篇文章。
300
+ > 更多关于 ` tracer ` 的内容,请参考[ trace] ( https://medium.com/a-journey-with-go/go-discovery-of-the-trace-package-e5a821743c3c ) 这篇文章。
301
301
302
- 下图是使用` atomic ` 包时,使用` pprof ` 分析后得到profile结果 :
302
+ 下图是使用 ` atomic ` 包时,使用 ` pprof ` 分析后得到 profile 结果 :
303
303
304
- ![ ] ( https://gitee. com/double12gzh/wiki-pictures/raw/ master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/%E5%9B%BE1 .png )
304
+ ![ ] ( https://raw.githubusercontent. com/studygolang/gctt-images2/ master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/2 .png )
305
305
306
- goroutines运行时不间断 ,能够完成任务。对于带有` mutex ` 的程序的配置文件,得到的结果那是完全不同的。
306
+ goroutines 运行时不间断 ,能够完成任务。对于带有 ` mutex ` 的程序的配置文件,得到的结果那是完全不同的。
307
307
308
- ![ ] ( https://gitee. com/double12gzh/wiki-pictures/raw/ master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/%E5%9B%BE2 .png )
308
+ ![ ] ( https://raw.githubusercontent. com/studygolang/gctt-images2/ master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/3 .png )
309
309
310
- 现在运行时间相当零碎,这是由于停放goroutine的mutex造成的。这一点可以从goroutine的概览中得到证实 ,其中显示了同步时被阻塞的时间(如下图)。
310
+ 现在运行时间相当零碎,这是由于停放 goroutine 的 mutex 造成的。这一点可以从 goroutine 的概览中得到证实 ,其中显示了同步时被阻塞的时间(如下图)。
311
311
312
- ![ ] ( https://gitee. com/double12gzh/wiki-pictures/raw/ master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/%E5%9B%BE3 .png )
312
+ ![ ] ( https://raw.githubusercontent. com/studygolang/gctt-images2/ master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/4 .png )
313
313
314
- 屏蔽时间大概占到三分之一的时间,这一点可以从下面的block profile的图中详细看到 。
314
+ 屏蔽时间大概占到三分之一的时间,这一点可以从下面的 block profile 的图中详细看到 。
315
315
316
- ![ ] ( https://gitee. com/double12gzh/wiki-pictures/raw/ master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/%E5%9B%BE4 .png )
316
+ ![ ] ( https://raw.githubusercontent. com/studygolang/gctt-images2/ master/Go-How-to-Reduce-Lock-Contention-with-the-Atomic-Package/5 .png )
317
317
318
- 在这种情况下,` atomic ` 包肯定会带来优势。但是,在某些方面可能会降低性能。例如,如果你要存储一张大地图,每次更新地图时都要复制它,这样效率就很低。
318
+ 在这种情况下,` atomic ` 包肯定会带来优势。但是,在某些方面可能会降低性能。例如,如果你要存储一张大地图,每次更新地图时都要复制它,这样效率就很低。
319
319
320
- > 更多关于` mutex ` 的内容可以参考[ Go: Mutex and Starvation] ( https://medium.com/a-journey-with-go/go-mutex-and-starvation-3f4f4e75ad50 )
320
+ > 更多关于 ` mutex ` 的内容可以参考[ Go: Mutex and Starvation] ( https://medium.com/a-journey-with-go/go-mutex-and-starvation-3f4f4e75ad50 )
321
321
322
322
---
323
+
323
324
via: https://medium.com/a-journey-with-go/go-how-to-reduce-lock-contention-with-the-atomic-package-ba3b2664b549
324
325
325
326
作者:[ Vincent Blanchon] ( https://medium.com/@blanchon.vincent )
326
327
译者:[ double12gzh] ( https://github.com/double12gzh )
327
- 校对:[ 校对者ID ] ( https://github.com/校对者ID )
328
+ 校对:[ lxbwolf ] ( https://github.com/lxbwolf )
328
329
329
330
本文由 [ GCTT] ( https://github.com/studygolang/GCTT ) 原创编译,[ Go 中文网] ( https://studygolang.com/ ) 荣誉推出
0 commit comments