數(shù)據(jù)競(jìng)爭(zhēng)和內(nèi)存重分配Golang slice并發(fā)不安全問題解決
Golang 中的 slice 為什么是并發(fā)不安全的?
一、并發(fā)不安全的
在Go語言中,slice是并發(fā)不安全的,主要有以下兩個(gè)原因:數(shù)據(jù)競(jìng)爭(zhēng)、內(nèi)存重分配。
數(shù)據(jù)競(jìng)爭(zhēng):slice底層的結(jié)構(gòu)體包含一個(gè)指向底層數(shù)組的指針和該數(shù)組的長(zhǎng)度,當(dāng)多個(gè)協(xié)程并發(fā)訪問同一個(gè)slice時(shí),有可能會(huì)出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)的問題。例如,一個(gè)協(xié)程在修改slice的長(zhǎng)度,而另一個(gè)協(xié)程同時(shí)在讀取或修改slice的內(nèi)容。
內(nèi)存重分配:在向slice中追加元素時(shí),可能會(huì)觸發(fā)slice的擴(kuò)容操作,在這個(gè)過程中,如果有其他協(xié)程訪問了slice,就會(huì)導(dǎo)致指向底層數(shù)組的指針出現(xiàn)異常。
二、并發(fā)場(chǎng)景
多個(gè)協(xié)程同時(shí)向 slice 追加元素,會(huì)有一部分元素被追加到了舊的底層數(shù)組里,最終 slice 的長(zhǎng)度小于目標(biāo)值。
func main() {
a := make([]int, 0)
for i := 0; i < 10000; i++ {
go func(i int) {
a = append(a, i)
}(i)
}
fmt.Println(len(a)) // 9015 < 10000
}三、實(shí)現(xiàn) slice 并發(fā)安全
要實(shí)現(xiàn) slice 并發(fā)安全,有兩種方法:加互斥鎖、使用channel串行化操作。
方式一:使用互斥鎖 sync.Mutex
追加元素之前調(diào)用 Lock() 函數(shù)加鎖,追加完后,調(diào)用 Unlock() 解鎖。
func main() {
var lock sync.Mutex //互斥鎖
a := make([]int, 0)
var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
lock.Lock()
defer lock.Unlock()
a = append(a, i)
}(i)
}
wg.Wait()
fmt.Println(len(a))
// equal 10000
}最終 slice 的長(zhǎng)度等于目標(biāo)值。
方式二:使用channel串行化操作
生產(chǎn)者生產(chǎn)元素,發(fā)送到通道中,消費(fèi)者從通道中接收元素,追加到 slice 中。使用無緩沖通道,接收方、發(fā)送方必須同時(shí)存在,負(fù)責(zé)任意一方都會(huì)阻塞。
func main() {
buffer := make(chan int)
a := make([]int, 0)
// 消費(fèi)者
go func() {
for v := range buffer {
a = append(a, v)
}
}()
// 生產(chǎn)者
var wg sync.WaitGroup
for i := 0; i < 10000; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
buffer <- i
}(i)
}
wg.Wait()
fmt.Println(len(a))
// equal 10000
}最終 slice 的長(zhǎng)度等于目標(biāo)值。
兩種方式的比較
加互斥鎖適合于對(duì)性能要求不高的場(chǎng)景,畢竟鎖的粒度太大,這種方式屬于通過共享內(nèi)存來實(shí)現(xiàn)通信。channle 適合于對(duì)性能要求大的場(chǎng)景,channle 就是專用于 goroutine 間通信的,這種方式屬于通過通信來實(shí)現(xiàn)共享內(nèi)存。
以上就是數(shù)據(jù)競(jìng)爭(zhēng)和內(nèi)存重分配Golang slice并發(fā)不安全問題解決的詳細(xì)內(nèi)容,更多關(guān)于Golang slice并發(fā)安全的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MacOS中 VSCode 安裝 GO 插件失敗問題的快速解決方法
這篇文章主要介紹了MacOS中 VSCode 安裝 GO 插件失敗問題的快速解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
詳解Go中如何進(jìn)行進(jìn)行內(nèi)存優(yōu)化和垃圾收集器管理
這篇文章主要為大家詳細(xì)介紹了Go中如何進(jìn)行進(jìn)行內(nèi)存優(yōu)化和垃圾收集器管理,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解下2023-11-11
Golang中文件目錄操作的實(shí)現(xiàn)步驟詳解
在Golang中,文件目錄是指計(jì)算機(jī)文件系統(tǒng)中的文件夾或目錄。目錄是用于組織和存儲(chǔ)文件的一種方式,可以包含文件和其他子目錄,本文主要介紹了Golang中文件目錄操作的實(shí)現(xiàn)方法,需要的朋友可以參考下2023-05-05
golang之?dāng)?shù)據(jù)驗(yàn)證validator的實(shí)現(xiàn)
這篇文章主要介紹了golang之?dāng)?shù)據(jù)驗(yàn)證validator的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
go語言中嵌套結(jié)構(gòu)體的實(shí)現(xiàn)
在Go語言中,嵌套結(jié)構(gòu)體可定義為一個(gè)結(jié)構(gòu)體內(nèi)包含另一個(gè)結(jié)構(gòu)體,嵌套可以是值嵌套或指針嵌套,兩者在內(nèi)存分配和修改影響上有顯著區(qū)別,本文就來詳細(xì)的介紹一下,感興趣的可以了解一下2024-09-09
golang 實(shí)現(xiàn)tcp server端和client端,并計(jì)算RTT時(shí)間操作
這篇文章主要介紹了golang 實(shí)現(xiàn)tcp server端和client端,并計(jì)算RTT時(shí)間操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12
Golang算法問題之整數(shù)拆分實(shí)現(xiàn)方法分析
這篇文章主要介紹了Golang算法問題之整數(shù)拆分實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了Go語言數(shù)值運(yùn)算與數(shù)組遍歷相關(guān)操作技巧,需要的朋友可以參考下2017-02-02
Go語言常見錯(cuò)誤之將接口定義在實(shí)現(xiàn)方
在Go中,接口起到一個(gè)十分關(guān)鍵的角色,它們提供了一種方式來定義對(duì)象的行為,而不需要知道對(duì)象的具體實(shí)現(xiàn),一個(gè)常見的錯(cuò)誤是在實(shí)現(xiàn)方而不是使用方定義接口,本文將詳細(xì)探討為何這樣做是一個(gè)錯(cuò)誤,以及如何避免它2024-01-01

