Go語言切片原生支持并發(fā)嗎?
實踐檢驗真理
實踐是檢驗真理的唯一標(biāo)準(zhǔn),所以當(dāng)我們遇到一個不確定的問題,直接寫demo來驗證,因為切片的特點,我們可以分多種情況來驗證:
- 不指定索引,動態(tài)擴(kuò)容并發(fā)向切片添加數(shù)據(jù)。
func concurrentAppendSliceNotForceIndex() {
sl := make([]int, 0)
wg := sync.WaitGroup{}
for index := 0; index < 100; index++{
k := index
wg.Add(1)
go func(num int) {
sl = append(sl, num)
wg.Done()
}(k)
}
wg.Wait()
fmt.Printf("final len(sl)=%d cap(sl)=%d\n", len(sl), cap(sl))
}
通過打印數(shù)據(jù)發(fā)現(xiàn)每次的結(jié)果都不一致,先不急出結(jié)論,我們在寫其他的demo測試一下:
- 指定索引,指定容量并發(fā)向切片添加數(shù)據(jù)。
func concurrentAppendSliceForceIndex() {
sl := make([]int, 100)
wg := sync.WaitGroup{}
for index := 0; index < 100; index++{
k := index
wg.Add(1)
go func(num int) {
sl[num] = num
wg.Done()
}(k)
}
wg.Wait()
fmt.Printf("final len(sl)=%d cap(sl)=%d\n", len(sl), cap(sl))
}
通過結(jié)果我們可以發(fā)現(xiàn)符合我們的預(yù)期,長度和容量都是100,所以說slice支持并發(fā)嗎?
slice支持并發(fā)嗎?
我們都知道切片是對數(shù)組的抽象,其底層就是數(shù)組,在并發(fā)下寫數(shù)據(jù)到相同的索引位會被覆蓋,并且切片也有自動擴(kuò)容的功能,當(dāng)切片要進(jìn)行擴(kuò)容時,就要替換底層的數(shù)組,在切換底層數(shù)組時,多個goroutine是同時運行的,哪個goroutine先運行是不確定的,不論哪個goroutine先寫入內(nèi)存,肯定就有一次寫入會覆蓋之前的寫入,所以在動態(tài)擴(kuò)容時并發(fā)寫入數(shù)組是不安全的;
所以當(dāng)別人問你slice支持并發(fā)時,你就可以這樣回答它:
當(dāng)指定索引使用切片時,切片是支持并發(fā)讀寫索引區(qū)的數(shù)據(jù)的,但是索引區(qū)的數(shù)據(jù)在并發(fā)時會被覆蓋的;當(dāng)不指定索引切片時,并且切片動態(tài)擴(kuò)容時,并發(fā)場景下擴(kuò)容會被覆蓋,所以切片是不支持并發(fā)的~。
github上著名的iris框架也曾遇到過切片動態(tài)擴(kuò)容導(dǎo)致webscoket連接數(shù)減少的bug,最終采用sync.map解決了該問題。
總結(jié)
針對上述問題,我們可以多種方法來解決切片并發(fā)安全的問題:
- 加互斥鎖
- 使用channel串行化操作
- 使用sync.map代替切片
切片的問題還是比較容易解決,針對不同的場景可以選擇不同的方案進(jìn)行優(yōu)化,你學(xué)會了嗎?