Go語言單線程運行也會有的并發(fā)問題解析
Go單線程多goroutine訪問一個map會遇到并發(fā)讀寫panic么?
一個 Go 大佬群中嚴肅的討論了一個問題:Go 程序單線程多 goroutine 訪問一個 map 會遇到并發(fā)讀寫 panic 么?
答案是肯定的,因為出現(xiàn)了這個問題所以大家才在群中討論。
為什么呢?因為單線程意味著并行單元只有一個(多線程也可能并行單元只有一個),但是多 goroutine 意味著并發(fā)單元有多個,如果并發(fā)單元同時執(zhí)行,即使是單線程,可能就會產(chǎn)生數(shù)據(jù)競爭的問題,除非這些 goroutine 是順序執(zhí)行的。
舉一個例子哈:
func TestCounter() { runtime.GOMAXPROCS(1) var counter int var wg sync.WaitGroup wg.Add(10) for i := 0; i < 10; i++ { i := i go func() { fmt.Printf("start task#%d, counter: %d\n", i, counter) for j := 0; j < 10_0000; j++ { counter++ } fmt.Printf("end task#%d, counter: %d\n", i, counter) wg.Done() }() } wg.Wait() fmt.Println(counter) }
這段測試代碼是啟動 10 個 goroutine 對計數(shù)器加一,每個 goroutine 負責加 10 萬次。在我的 MBP m1 筆記本上,每次的結果都是 100 萬,符合期望。如果你運行這段代碼,會發(fā)現(xiàn) goroutine 其實是一個一個串行執(zhí)行的(9->0->1->2->3->4->5->6->7->8,當然可能在你的機器上不是這樣的),如果是串行執(zhí)行,不會有并發(fā)問題:
start task#9, counter: 0
end task#9, counter: 100000
start task#0, counter: 100000
end task#0, counter: 200000
start task#1, counter: 200000
end task#1, counter: 300000
start task#2, counter: 300000
end task#2, counter: 400000
start task#3, counter: 400000
end task#3, counter: 500000
start task#4, counter: 500000
end task#4, counter: 600000
start task#5, counter: 600000
end task#5, counter: 700000
start task#6, counter: 700000
end task#6, counter: 800000
start task#7, counter: 800000
end task#7, counter: 900000
start task#8, counter: 900000
end task#8, counter: 1000000
1000000
插入runtime.Gosched()
為了制造點緊張氣氛,我將代碼改寫成下面這樣子,將counter++
三條指令明顯寫成三條語句,并在中間插入runtime.Gosched()
,故意給其它 goroutine 的執(zhí)行制造機會:
func TestCounter2() { runtime.GOMAXPROCS(1) var counter int var wg sync.WaitGroup wg.Add(10) for i := 0; i < 10; i++ { i := i go func() { fmt.Printf("start task#%d, counter: %d\n", i, counter) for j := 0; j < 10_0000; j++ { temp := counter runtime.Gosched() temp = temp + 1 counter = temp } fmt.Printf("end task#%d, counter: %d\n", i, counter) wg.Done() }() } wg.Wait() fmt.Println(counter) }
運行這段代碼,你就會明顯看到數(shù)據(jù)不一致的效果,即使是單個線程運行 goroutine,也出現(xiàn)了數(shù)據(jù)競爭的問題:
start task#9, counter: 0
start task#0, counter: 0
start task#1, counter: 0
start task#2, counter: 0
start task#3, counter: 0
start task#4, counter: 0
start task#5, counter: 0
start task#6, counter: 0
start task#7, counter: 0
start task#8, counter: 0
end task#9, counter: 100000
end task#1, counter: 100000
end task#3, counter: 100000
end task#2, counter: 100000
end task#5, counter: 100000
end task#0, counter: 100000
end task#4, counter: 100000
end task#6, counter: 100000
end task#7, counter: 100000
end task#8, counter: 100000
100000
這個結果非常離譜,期望 100 萬,最后只有 10 萬。
訪問同一個 map對象也有可能出現(xiàn)并發(fā)bug
因為單個線程運行多個 goroutine 會有數(shù)據(jù)競爭的問題,所以訪問同一個 map 對象也有可能出現(xiàn)并發(fā) bug,比如下面的代碼,10 個 goroutine 并發(fā)的寫同一個 map:
func TestMap() { var m = make(map[int]int) var wg sync.WaitGroup wg.Add(10) for i := 0; i < 10; i++ { i := i go func() { fmt.Printf("start map task#%d, m: %v\n", i, len(m)) for j := 0; j < 10_0000; j++ { m[j] = i*10_0000 + j } fmt.Printf("end map task#%d, m: %v\n", i, len(m)) wg.Done() }() } wg.Wait() }
大概率會出現(xiàn) panic:
start map task#9, m: 0 start map task#0, m: 49152 fatal error: concurrent map writes goroutine 41 [running]: main.TestMap.func1() /Users/chaoyuepan/study/single_thread/main.go:72 +0xcc created by main.TestMap in goroutine 1 /Users/chaoyuepan/study/single_thread/main.go:69 +0x4c goroutine 1 [semacquire]: sync.runtime_Semacquire(0x140000021a0?) /usr/local/go/src/runtime/sema.go:62 +0x2c sync.(*WaitGroup).Wait(0x1400000e1d0) /usr/local/go/src/sync/waitgroup.go:116 +0x74 main.TestMap() /Users/chaoyuepan/study/single_thread/main.go:79 +0xb8 main.main() /Users/chaoyuepan/study/single_thread/main.go:15 +0x2c
以上就是Go語言單線程運行的并發(fā)問題解析的詳細內(nèi)容,更多關于Go單線程運行并發(fā)問題的資料請關注腳本之家其它相關文章!
相關文章
golang?db事務的統(tǒng)一封裝的實現(xiàn)
這篇文章主要介紹了golang db事務的統(tǒng)一封裝的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12