Sync.atomic vs Sync.mutex
sync.atomic: https://pkg.go.dev/sync/atomic
sync.mutex: https://pkg.go.dev/sync#Mutex
寫多讀少 -> 互斥鎖
寫少讀多 -> 原子操作, 讀寫鎖
Sync.atomic vs Sync.mutex
Mutex vs Atomic 的情況裡,Mutex 相對更重。
因為涉及到更多的 goroutine 之間的上下文切換 pack blocking goroutine,以及喚醒 goroutine。
main.go
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
| package main
import (
"fmt"
"log"
"net/http"
_ "net/http/pprof"
"sync"
"sync/atomic"
)
type Config struct {
a []int
}
func (c *Config) T() {}
func Atomic(numbers int) {
var v atomic.Value
v.Store(&Config{})
go func() {
i := 0
for {
i++
cfg := &Config{a: []int{i, i + 1, i + 2, i + 3, i + 4, i + 5}}
v.Store(cfg)
}
}()
var wg sync.WaitGroup
for n := 0; n < 4; n++ {
wg.Add(1)
go func() {
defer wg.Done()
for n := 0; n < numbers; n++ {
cfg := v.Load().(*Config)
cfg.T()
// fmt.Printf("%v\n", cfg)
}
}()
}
wg.Wait()
}
func RWMutex(numbers int) {
var rwmu sync.RWMutex
var cfg *Config
go func() {
i := 0
for {
i++
rwmu.Lock()
cfg = &Config{a: []int{i, i + 1, i + 2, i + 3, i + 4, i + 5}}
rwmu.Unlock()
}
}()
var wg sync.WaitGroup
for n := 0; n < 4; n++ {
wg.Add(1)
go func() {
defer wg.Done()
for n := 0; n < numbers; n++ {
rwmu.RLock()
cfg.T()
rwmu.RUnlock()
// fmt.Printf("%v\n", cfg)
}
}()
}
wg.Wait()
}
func main() {
Atomic(100)
RWMutex(100)
log.Fatalln(http.ListenAndServe("localhost:9999", nil))
}
|
main_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
| package main
import "testing"
func BenchmarkAtomic(b *testing.B) {
b.ResetTimer()
Atomic(b.N)
}
func BenchmarkRWMutex(b *testing.B) {
b.ResetTimer()
RWMutex(b.N)
}
|
從結果來看 atomic 的效能快 mutex很多~
1
2
3
4
5
6
7
8
| goos: darwin
goarch: amd64
pkg: MyGoNote/Golang/Package_Sync/examples/atomic_mutex
cpu: Intel(R) Core(TM) i5-8259U CPU @ 2.30GHz
BenchmarkAtomic-8 552392600 2.861 ns/op 1 B/op 0 allocs/op
BenchmarkRWMutex-8 753264 1415 ns/op 1967 B/op 54 allocs/op
PASS
ok MyGoNote/Golang/Package_Sync/examples/atomic_mutex 3.655s
|
sync.atoml
Copy-On-Write(COW) 思路在微服務降級或者 local cache 場景中經常使用。
寫時複製(Copy-On-Write)指的是,寫操作時候複製全量老數據到一個新的對象中,
攜帶上本次新寫的數據,之後利用原子替換(atomic.Value),
更新調用者的變量。來完成無鎖訪問共享數據。
wiki: cow
寫入時複製(英語:Copy-on-write,簡稱COW)是一種電腦程式設計領域的最佳化策略。其核心思想是,
如果有多個呼叫者(callers)同時請求相同資源(如記憶體或磁碟上的資料儲存),他們會共同取得相同的指標指向相同的資源,
直到某個呼叫者試圖修改資源的內容時,系統才會真正複製一份專用副本(private copy)給該呼叫者,
而其他呼叫者所見到的最初的資源仍然保持不變。這過程對其他的呼叫者都是透明的。
此作法主要的優點是如果呼叫者沒有修改該資源,就不會有副本(private copy)被建立,因此多個呼叫者只是讀取操作時可以共享同一份資源。
pprof
如果使用 benchmark的方式來評估效能, 最主要遇到的問題會是,在一大段程式碼及邏輯中,要找出慢的主因.
這時可以使用到 pprof
來找出程式碼所有執行的時間,怎麼輸出 CPU 所花的時間,可以透過底下指令:
1
| go test -bench=. -benchtime=3s -cpuprofile cpu.out .
|
產生.out
後可以用 go tool pprof cpu.out
來看
1
2
3
4
5
6
| $ go tool pprof cpu.out
Type: cpu
Time: Aug 24, 2022 at 10:41am (CST)
Duration: 11.74s, Total samples = 32.51s (276.81%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
|
使用 top 來看數據
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| (pprof) top 10
Showing nodes accounting for 20040ms, 61.64% of 32510ms total
Dropped 153 nodes (cum <= 162.55ms)
Showing top 10 nodes out of 98
flat flat% sum% cum cum%
6370ms 19.59% 19.59% 6380ms 19.62% runtime.madvise
2590ms 7.97% 27.56% 9010ms 27.71% runtime.mallocgc
2080ms 6.40% 33.96% 2520ms 7.75% sync/atomic.(*Value).Load (inline)
1960ms 6.03% 39.99% 4950ms 15.23% MyGoNote/Golang/Package_Sync/examples/atomic_mutex.Atomic.func2
1820ms 5.60% 45.59% 1840ms 5.66% runtime.usleep
1660ms 5.11% 50.69% 1670ms 5.14% runtime.pageIndexOf (inline)
1140ms 3.51% 54.20% 1140ms 3.51% runtime.asyncPreempt
880ms 2.71% 56.91% 880ms 2.71% runtime.procyield
810ms 2.49% 59.40% 1550ms 4.77% sync/atomic.StorePointer
730ms 2.25% 61.64% 730ms 2.25% runtime.memclrNoHeapPointers
(pprof) %
|
看atom function
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
| (pprof) list atom
Total: 32.51s
ROUTINE ======================== MyGoNote/Golang/Package_Sync/examples/atomic_mutex.Atomic.func1 in /Users/kimi/go/src/MyGoNote/Golang/Package_Sync/examples/atomic_mutex/main.go
450ms 9.43s (flat, cum) 29.01% of Total
. . 43: v.Store(&Config{})
. . 44:
. . 45: go func() {
. . 46: i := 0
. . 47: for {
40ms 50ms 48: i++
370ms 7.40s 49: cfg := &Config{a: []int{i, i + 1, i + 2, i + 3, i + 4, i + 5}}
40ms 1.98s 50: v.Store(cfg)
. . 51: }
. . 52: }()
. . 53:
. . 54: var wg sync.WaitGroup
. . 55: for n := 0; n < 4; n++ {
ROUTINE ======================== MyGoNote/Golang/Package_Sync/examples/atomic_mutex.Atomic.func2 in /Users/kimi/go/src/MyGoNote/Golang/Package_Sync/examples/atomic_mutex/main.go
1.96s 4.95s (flat, cum) 15.23% of Total
. . 54: var wg sync.WaitGroup
. . 55: for n := 0; n < 4; n++ {
. . 56: wg.Add(1)
. . 57: go func() {
. . 58: defer wg.Done()
1.10s 1.31s 59: for n := 0; n < numbers; n++ {
860ms 3.64s 60: cfg := v.Load().(*Config)
. . 61: cfg.T()
. . 62: // fmt.Printf("%v\n", cfg)
. . 63: }
. . 64: }()
. . 65: }
...
|
名稱 | 含義 |
---|
flat | 本函數的執行耗時 |
flat% | flat 佔 CPU 總時間的比例。程序總耗時 16.22s, Eat 的 16.19s 佔了 99.82% |
sum% | 前面每一行的 flat 佔比總和 |
cum | 累計量。指該函數加上該函數調用的函數總耗時 |
cum% | cum 佔 CPU 總時間的比例 |
pprof web 方式來進行 UI 操作
首先要先安裝 graphviz
https://graphviz.gitlab.io/download/
MAC
接下來輸入 go tool pprof -http=:8080 cpu.out
此時你就可以在你的瀏覽器上面 http://localhost:8080/ui/
看到
net/http/pprof
如果線上遇到 CPU 或內存佔用過高,該怎麼辦呢?總不能將上面的 Profile 代碼編譯到生產環境吧,這無疑會極大地影響性能。
net/http/pprof
提供了一個方法,不使用時不會造成任何影響,遇到問題時可以開啟 profiling 幫助我們排查問題。
我們只需要使用import這個包,然後在一個新的 goroutine 中調用http.ListenAndServe()在某個端口啟動一個默認的 HTTP 服務器即可:
1
2
3
4
5
6
7
8
9
10
11
12
| package main
import (
...
_ "net/http/pprof"
)
func main() {
...
log.Fatalln(http.ListenAndServe("localhost:9999", nil))
}
|
打開瀏覽器 http://localhost:9999/debug/pprof/
支援遠程調用
1
| go tool pprof -http :8080 http://localhost:9999/debug/pprof/allocs\?debug\=1
|
打開瀏覽器 http://localhost:8080/ui
Reference