Will Go single-threaded multi-goroutine access to a map encounter concurrent read and write panic?
A Go boss group seriously discussed a question: Will Go programs encounter concurrent read and write panic when accessing a map with a single thread and multiple goroutine?
The answer is yes, because this question arises, so everyone discusses it in the group.
Why? Because single thread means that there is only one parallel unit (multi-threading may also have only one parallel unit), but multiple goroutines means that there are multiple concurrent units. If concurrent units execute at the same time, even if it is a single thread, data competition may occur, unless these goroutines are executed sequentially.
Let me give you an example:
func TestCounter() { (1) var counter int var wg (10) for i := 0; i < 10; i++ { i := i go func() { ("start task#%d, counter: %d\n", i, counter) for j := 0; j < 10_0000; j++ { counter++ } ("end task#%d, counter: %d\n", i, counter) () }() } () (counter) }
This test code starts 10 goroutines to add one to the counter, and each goroutine is responsible for adding 100,000 times. On my MBP m1 notebook, the result is 1 million each time, which meets expectations. If you run this code, you will find that goroutine is actually executed one by one in serial (9->0->1->2->3->4->5->6->7->8, of course it may not be like this on your machine). If it is executed in serial, there will be no concurrency problems:
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
insert()
In order to create a little tension, I rewrite the code to the following,counter++
Three instructions are clearly written into three sentences and inserted in the middle.()
, deliberately give other goroutine execution manufacturing opportunities:
func TestCounter2() { (1) var counter int var wg (10) for i := 0; i < 10; i++ { i := i go func() { ("start task#%d, counter: %d\n", i, counter) for j := 0; j < 10_0000; j++ { temp := counter () temp = temp + 1 counter = temp } ("end task#%d, counter: %d\n", i, counter) () }() } () (counter) }
Run this code and you will obviously see the effect of data inconsistency. Even if a single thread runs goroutine, there will be data competition problems:
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
This result is very outrageous, expecting 1 million, and finally only 100,000.
There may also be concurrent bugs when accessing the same map object
Because a single thread runs multiple goroutines and will cause data competition, there may be concurrent bugs when accessing the same map object. For example, in the following code, 10 goroutines are written concurrently with the same map:
func TestMap() { var m = make(map[int]int) var wg (10) for i := 0; i < 10; i++ { i := i go func() { ("start map task#%d, m: %v\n", i, len(m)) for j := 0; j < 10_0000; j++ { m[j] = i*10_0000 + j } ("end map task#%d, m: %v\n", i, len(m)) () }() } () }
There is a high probability that panic will appear:
start map task#9, m: 0 start map task#0, m: 49152 fatal error: concurrent map writes goroutine 41 [running]: .func1() /Users/chaoyuepan/study/single_thread/:72 +0xcc created by in goroutine 1 /Users/chaoyuepan/study/single_thread/:69 +0x4c goroutine 1 [semacquire]: sync.runtime_Semacquire(0x140000021a0?) /usr/local/go/src/runtime/:62 +0x2c sync.(*WaitGroup).Wait(0x1400000e1d0) /usr/local/go/src/sync/:116 +0x74 () /Users/chaoyuepan/study/single_thread/:79 +0xb8 () /Users/chaoyuepan/study/single_thread/:15 +0x2c
The above is the detailed analysis of the concurrency problem analysis of Go single-threaded operation. For more information about Go single-threaded operation concurrency problem, please pay attention to my other related articles!