第九篇文章我们学习了Go解决并发编程问题的三种方式。归根结底都是采用加锁,让并发变成同步访问。那么这些处理方式性能如何呢?
先介绍一个Linux下面统计时间的命令time
,具体可以参考:https://www.runoob.com/linux/linux-comm-time.html
这里做一个测试,启动两个协程,一个对数执行++
操作,一个对数进行--
操作,因为循环的次数是相同的,最后这个数的值还是0。下面看看不同方式的代码,以及编译之后的执行性能。
Channel方式
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
|
package main
import (
"sync"
)
var loopTimes = 2000*10000
func main() {
num, wg, mux := new(int), new(sync.WaitGroup), make(chan bool, 1)
defer close(mux)
wg.Add(2)
go func(num *int, wg *sync.WaitGroup, mux chan bool) {
defer wg.Done()
for i := 1; i <= loopTimes; i++ {
mux <- true
*num++
<-mux
}
}(num, wg, mux)
go func(num *int, wg *sync.WaitGroup, mux chan bool) {
defer wg.Done()
for i := 1; i <= loopTimes; i++ {
mux <- true
*num--
<-mux
}
}(num, wg, mux)
wg.Wait()
println(*num)
}
|
在Linux服务器中保存上面代码channel.go
,命令行编译go build channel.go
,这样在当前目录下就会生成channel
可执行文件,用time ./channel
运行看看结果:
0
real 0m15.523s
user 0m17.485s
sys 0m0.949s
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
|
package main
import "sync"
var loopTimes = 2000*10000
func main() {
num, wg, mux := new(int), new(sync.WaitGroup), new(sync.Mutex)
wg.Add(2)
go func(num *int, wg *sync.WaitGroup, mux *sync.Mutex) {
defer wg.Done()
for i := 1; i <= loopTimes; i++ {
mux.Lock()
*num++
mux.Unlock()
}
}(num, wg, mux)
go func(num *int, wg *sync.WaitGroup, mux *sync.Mutex) {
defer wg.Done()
for i := 1; i <= loopTimes; i++ {
mux.Lock()
*num--
mux.Unlock()
}
}(num, wg, mux)
wg.Wait()
println(*num)
}
|
运行结果time ./mutex:
1
2
3
4
5
|
0
real 0m2.029s
user 0m3.994s
sys 0m0.012s
|
Atomic方式
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
|
package main
import (
"sync"
"sync/atomic"
)
var loopTimes = 2000*10000
func main() {
num, wg := new(int64), new(sync.WaitGroup)
wg.Add(2)
go func(num *int64, wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= loopTimes; i++ {
atomic.AddInt64(num, 1)
}
}(num, wg)
go func(num *int64, wg *sync.WaitGroup) {
defer wg.Done()
for i := 1; i <= loopTimes; i++ {
atomic.AddInt64(num, -1)
}
}(num, wg)
wg.Wait()
println(*num)
}
|
运行结果time ./atomic:
0
real 0m1.328s
user 0m2.605s
sys 0m0.004s
总结
看到上面的运行结果是不是很惊讶?Go语言一大特性,居然性能这么差。那我们还用这个特性干啥呢?
答案是:能不用就不用。
请看这篇文章:https://zhuanlan.zhihu.com/p/341931729
或者原文地址:https://linux.cn/article-12984-1.html
在幕后,通道使用锁来序列化访问并提供线程安全性。 因此,通过使用通道同步对内存的访问,你实际上就是在使用锁。 被包装在线程安全队列中的锁。 那么,与仅仅使用标准库 sync
包中的互斥量相比,Go 的花式锁又如何呢? 以下数字是通过使用 Go 的内置基准测试功能,对它们的单个集合连续调用 Put 得出的。
1
2
|
> BenchmarkSimpleSet-8 3000000 391 ns/op
> BenchmarkSimpleChannelSet-8 1000000 1699 ns/o
|
无缓冲通道的情况与此类似,甚至是在争用而不是串行运行的情况下执行相同的测试。
也许 Go 调度器会有所改进,但与此同时,良好的旧互斥量和条件变量是非常好、高效且快速。如果你想要提高性能,请使用久经考验的方法。
网上还有一个有意思的总结,可以参考:
(完)