您好,登錄后才能下訂單哦!
這篇文章主要介紹“Go并發(fā)編程時(shí)怎么避免發(fā)生競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)”的相關(guān)知識(shí),小編通過(guò)實(shí)際案例向大家展示操作過(guò)程,操作方法簡(jiǎn)單快捷,實(shí)用性強(qiáng),希望這篇“Go并發(fā)編程時(shí)怎么避免發(fā)生競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)”文章能幫助大家解決問(wèn)題。
多個(gè) goroutine 對(duì)同一變量進(jìn)行讀寫(xiě)操作。例如,多個(gè) goroutine 同時(shí)對(duì)一個(gè)計(jì)數(shù)器變量進(jìn)行增加操作。
多個(gè) goroutine 同時(shí)對(duì)同一數(shù)組、切片或映射進(jìn)行讀寫(xiě)操作。例如,多個(gè) goroutine 同時(shí)對(duì)一個(gè)切片進(jìn)行添加或刪除元素的操作。
多個(gè) goroutine 同時(shí)對(duì)同一文件進(jìn)行讀寫(xiě)操作。例如,多個(gè) goroutine 同時(shí)向同一個(gè)文件中寫(xiě)入數(shù)據(jù)。
多個(gè) goroutine 同時(shí)對(duì)同一網(wǎng)絡(luò)連接進(jìn)行讀寫(xiě)操作。例如,多個(gè) goroutine 同時(shí)向同一個(gè) TCP 連接中寫(xiě)入數(shù)據(jù)。
多個(gè) goroutine 同時(shí)對(duì)同一通道進(jìn)行讀寫(xiě)操作。例如,多個(gè) goroutine 同時(shí)向同一個(gè)無(wú)緩沖通道中發(fā)送數(shù)據(jù)或接收數(shù)據(jù)。
所以,我們要明白的一點(diǎn)是:只要多個(gè) goroutine 并發(fā)訪問(wèn)了共享資源,就有可能出現(xiàn)競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)。
現(xiàn)在,我們已經(jīng)知道了。在編寫(xiě)并發(fā)程序時(shí),如果不謹(jǐn)慎,沒(méi)有考慮清楚共享資源的訪問(wèn)方式和同步機(jī)制,那么就會(huì)發(fā)生競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)這些問(wèn)題,那么如何避免踩坑?避免發(fā)生競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)的辦法有哪些?請(qǐng)看下面:
互斥鎖:使用 sync 包中的 Mutex 或者 RWMutex,通過(guò)對(duì)共享資源加鎖來(lái)保證同一時(shí)間只有一個(gè) goroutine 訪問(wèn)。
讀寫(xiě)鎖:使用 sync 包中的 RWMutex,通過(guò)讀寫(xiě)鎖的機(jī)制來(lái)允許多個(gè) goroutine 同時(shí)讀取共享資源,但是只允許一個(gè) goroutine 寫(xiě)入共享資源。
原子操作:使用 sync/atomic 包中提供的原子操作,可以對(duì)共享變量進(jìn)行原子操作,從而保證不會(huì)出現(xiàn)競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)。
通道:使用 Go 語(yǔ)言中的通道機(jī)制,可以將數(shù)據(jù)通過(guò)通道傳遞,從而避免直接對(duì)共享資源的訪問(wèn)。
WaitGroup:使用 sync 包中的 WaitGroup,可以等待多個(gè) goroutine 完成后再繼續(xù)執(zhí)行,從而保證多個(gè) goroutine 之間的順序性。
Context:使用 context 包中的 Context,可以傳遞上下文信息并控制多個(gè) goroutine 的生命周期,從而避免出現(xiàn)因?yàn)槟硞€(gè) goroutine 阻塞導(dǎo)致整個(gè)程序阻塞的情況。
比如在一個(gè)Web服務(wù)器中,多個(gè)goroutine需要同時(shí)訪問(wèn)同一個(gè)全局計(jì)數(shù)器的變量,達(dá)到記錄網(wǎng)站訪問(wèn)量的目的。
在這種情況下,如果沒(méi)有對(duì)訪問(wèn)計(jì)數(shù)器的訪問(wèn)進(jìn)行同步和保護(hù),就會(huì)出現(xiàn)競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題。假設(shè)有兩個(gè)goroutine A和B,它們同時(shí)讀取計(jì)數(shù)器變量的值為N,然后都增加了1并把結(jié)果寫(xiě)回計(jì)數(shù)器,那么最終的計(jì)數(shù)器值只會(huì)增加1而不是2,這就是一個(gè)競(jìng)態(tài)條件。
為了解決這個(gè)問(wèn)題,可以使用鎖等機(jī)制來(lái)保證訪問(wèn)計(jì)數(shù)器的同步和互斥。在Go中,可以使用互斥鎖(sync.Mutex)來(lái)保護(hù)共享資源。當(dāng)一個(gè)goroutine需要訪問(wèn)共享資源時(shí),它需要先獲取鎖,然后訪問(wèn)資源并完成操作,最后釋放鎖。這樣就可以保證每次只有一個(gè)goroutine能夠訪問(wèn)共享資源,從而避免競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。
看下面的代碼:
package main import ( "fmt" "sync" ) var count int var mutex sync.Mutex func main() { var wg sync.WaitGroup // 啟動(dòng)10個(gè)goroutine并發(fā)增加計(jì)數(shù)器的值 for i := 0; i < 10; i++ { wg.Add(1) go func() { // 獲取鎖 mutex.Lock() // 訪問(wèn)計(jì)數(shù)器并增加值 count++ // 釋放鎖 mutex.Unlock() wg.Done() }() } // 等待所有g(shù)oroutine執(zhí)行完畢 wg.Wait() // 輸出計(jì)數(shù)器的最終值 fmt.Println(count) }
在上面的代碼中,使用了互斥鎖來(lái)保護(hù)計(jì)數(shù)器變量的訪問(wèn)。每個(gè)goroutine在訪問(wèn)計(jì)數(shù)器變量之前先獲取鎖,然后進(jìn)行計(jì)數(shù)器的增加操作,最后釋放鎖。這樣就可以保證計(jì)數(shù)器變量的一致性和正確性,避免競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。
具體的思路是,啟動(dòng)每個(gè) goroutine 時(shí)調(diào)用 wg.Add(1) 來(lái)增加等待組的計(jì)數(shù)器。然后,在所有 goroutine 執(zhí)行完畢后,調(diào)用 wg.Wait() 來(lái)等待它們完成。最后,輸出計(jì)數(shù)器的最終值。
請(qǐng)注意,這個(gè)假設(shè)的場(chǎng)景和這個(gè)代碼示例,僅僅只是是為了演示如何使用互斥鎖來(lái)保護(hù)共享資源,實(shí)際情況可能更加復(fù)雜。例如,在實(shí)際的運(yùn)維開(kāi)發(fā)中,如果使用鎖的次數(shù)過(guò)多,可能會(huì)影響程序的性能。因此,在實(shí)際開(kāi)發(fā)中,還需要根據(jù)具體情況選擇合適的同步機(jī)制來(lái)保證并發(fā)程序的正確性和性能。
下面是一個(gè)使用 sync 包中的 RWMutex 實(shí)現(xiàn)讀寫(xiě)鎖的代碼案例:
package main import ( "fmt" "sync" "time" ) var ( count int rwLock sync.RWMutex ) func readData() { // 讀取共享數(shù)據(jù),獲取讀鎖 rwLock.RLock() defer rwLock.RUnlock() fmt.Println("reading data...") time.Sleep(1 * time.Second) fmt.Printf("data is %d\n", count) } func writeData(n int) { // 寫(xiě)入共享數(shù)據(jù),獲取寫(xiě)鎖 rwLock.Lock() defer rwLock.Unlock() fmt.Println("writing data...") time.Sleep(1 * time.Second) count = n fmt.Printf("data is %d\n", count) } func main() { // 啟動(dòng) 5 個(gè)讀取協(xié)程 for i := 0; i < 5; i++ { go readData() } // 啟動(dòng) 2 個(gè)寫(xiě)入?yún)f(xié)程 for i := 0; i < 2; i++ { go writeData(i + 1) } // 等待所有協(xié)程結(jié)束 time.Sleep(5 * time.Second) }
在這個(gè)示例中,有 5 個(gè)讀取協(xié)程和 2 個(gè)寫(xiě)入?yún)f(xié)程,它們都會(huì)訪問(wèn)一個(gè)共享的變量 count。讀取協(xié)程使用 RLock() 方法獲取讀鎖,寫(xiě)入?yún)f(xié)程使用 Lock() 方法獲取寫(xiě)鎖。通過(guò)讀寫(xiě)鎖的機(jī)制,多個(gè)讀取協(xié)程可以同時(shí)讀取共享數(shù)據(jù),而寫(xiě)入?yún)f(xié)程則會(huì)等待讀取協(xié)程全部結(jié)束后才能執(zhí)行,從而避免了讀取協(xié)程在寫(xiě)入?yún)f(xié)程執(zhí)行過(guò)程中讀取到臟數(shù)據(jù)的問(wèn)題。
下面是一個(gè)使用 sync/atomic 包中提供的原子操作實(shí)現(xiàn)并發(fā)安全的計(jì)數(shù)器的代碼案例:
package main import ( "fmt" "sync/atomic" "time" ) func main() { var counter int64 // 啟動(dòng) 10 個(gè)協(xié)程對(duì)計(jì)數(shù)器進(jìn)行增量操作 for i := 0; i < 10; i++ { go func() { for j := 0; j < 100; j++ { atomic.AddInt64(&counter, 1) } }() } // 等待所有協(xié)程結(jié)束 time.Sleep(time.Second) // 輸出計(jì)數(shù)器的值 fmt.Printf("counter: %d\n", atomic.LoadInt64(&counter)) }
在這個(gè)示例中,有 10 個(gè)協(xié)程并發(fā)地對(duì)計(jì)數(shù)器進(jìn)行增量操作。由于多個(gè)協(xié)程同時(shí)對(duì)計(jì)數(shù)器進(jìn)行操作,如果不使用同步機(jī)制,就會(huì)出現(xiàn)競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)。為了保證程序的正確性和健壯性,使用了 sync/atomic 包中提供的原子操作,通過(guò) AddInt64() 方法對(duì)計(jì)數(shù)器進(jìn)行原子加操作,保證了計(jì)數(shù)器的并發(fā)安全。最后使用 LoadInt64() 方法獲取計(jì)數(shù)器的值并輸出。
下面是一個(gè)使用通道機(jī)制實(shí)現(xiàn)并發(fā)安全的計(jì)數(shù)器的代碼案例:
package main import ( "fmt" "sync" ) func main() { var counter int // 創(chuàng)建一個(gè)有緩沖的通道,容量為 10 ch := make(chan int, 10) // 創(chuàng)建一個(gè)等待組,用于等待所有協(xié)程完成 var wg sync.WaitGroup wg.Add(10) // 啟動(dòng) 10 個(gè)協(xié)程對(duì)計(jì)數(shù)器進(jìn)行增量操作 for i := 0; i < 10; i++ { go func() { for j := 0; j < 10; j++ { // 將增量操作發(fā)送到通道中 ch <- 1 } // 任務(wù)完成,向等待組發(fā)送信號(hào) wg.Done() }() } // 等待所有協(xié)程完成 wg.Wait() // 從通道中接收增量操作并累加到計(jì)數(shù)器中 for i := 0; i < 100; i++ { counter += <-ch } // 輸出計(jì)數(shù)器的值 fmt.Printf("counter: %d\n", counter) }
在這個(gè)示例中,有 10 個(gè)協(xié)程并發(fā)地對(duì)計(jì)數(shù)器進(jìn)行增量操作。為了避免直接對(duì)共享資源的訪問(wèn),使用了一個(gè)容量為 10 的有緩沖通道,將增量操作通過(guò)通道傳遞,然后在主協(xié)程中從通道中接收增量操作并累加到計(jì)數(shù)器中。在協(xié)程中使用了等待組等待所有協(xié)程完成任務(wù),保證了程序的正確性和健壯性。最后輸出計(jì)數(shù)器的值。
下面是一個(gè)使用 sync.WaitGroup 等待多個(gè) Goroutine 完成后再繼續(xù)執(zhí)行的代碼案例:
package main import ( "fmt" "sync" ) func main() { var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) // 計(jì)數(shù)器加1 go func(i int) { defer wg.Done() // 完成時(shí)計(jì)數(shù)器減1 fmt.Printf("goroutine %d is running\n", i) }(i) } wg.Wait() // 等待所有 Goroutine 完成 fmt.Println("all goroutines have completed") }
在這個(gè)示例中,有 3 個(gè) Goroutine 并發(fā)執(zhí)行,使用 wg.Add(1) 將計(jì)數(shù)器加1,表示有一個(gè) Goroutine 需要等待。在每個(gè) Goroutine 中使用 defer wg.Done() 表示任務(wù)完成,計(jì)數(shù)器減1。最后使用 wg.Wait() 等待所有 Goroutine 完成任務(wù),然后輸出 "all goroutines have completed"。
下面是一個(gè)使用 context.Context 控制多個(gè) Goroutine 的生命周期的代碼案例:
package main import ( "context" "fmt" "time" ) func worker(ctx context.Context, id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d started\n", id) for { select { case <-ctx.Done(): fmt.Printf("Worker %d stopped\n", id) return default: fmt.Printf("Worker %d is running\n", id) time.Sleep(time.Second) } } } func main() { ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup for i := 1; i <= 3; i++ { wg.Add(1) go worker(ctx, i, &wg) } time.Sleep(3 * time.Second) cancel() wg.Wait() fmt.Println("All workers have stopped") }
在這個(gè)示例中,使用 context.WithCancel 創(chuàng)建了一個(gè)上下文,并在 main 函數(shù)中傳遞給多個(gè) Goroutine。每個(gè) Goroutine 在一個(gè) for 循環(huán)中執(zhí)行任務(wù),如果收到了 ctx.Done() 信號(hào)就結(jié)束任務(wù)并退出循環(huán),否則就打印出正在運(yùn)行的信息并等待一段時(shí)間。在 main 函數(shù)中,通過(guò)調(diào)用 cancel() 來(lái)發(fā)送一個(gè)信號(hào),通知所有 Goroutine 結(jié)束任務(wù)。使用 sync.WaitGroup 等待所有 Goroutine 結(jié)束任務(wù),然后輸出 "All workers have stopped"。
關(guān)于“Go并發(fā)編程時(shí)怎么避免發(fā)生競(jìng)態(tài)條件和數(shù)據(jù)競(jìng)爭(zhēng)”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí),可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識(shí)點(diǎn)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。