欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Go并發(fā)編程時(shí)如何避免發(fā)生競態(tài)條件和數(shù)據(jù)競爭

 更新時(shí)間:2023年04月26日 09:24:33   作者:不背鍋運(yùn)維  
大家都知道,Go是一種支持并發(fā)編程的編程語言,但并發(fā)編程也是比較復(fù)雜和容易出錯(cuò)的。比如本篇分享的問題:競態(tài)條件和數(shù)據(jù)競爭的問題

會(huì)發(fā)生競態(tài)條件和數(shù)據(jù)競爭的場景有哪些

  • 多個(gè) goroutine 對同一變量進(jìn)行讀寫操作。例如,多個(gè) goroutine 同時(shí)對一個(gè)計(jì)數(shù)器變量進(jìn)行增加操作。
  • 多個(gè) goroutine 同時(shí)對同一數(shù)組、切片或映射進(jìn)行讀寫操作。例如,多個(gè) goroutine 同時(shí)對一個(gè)切片進(jìn)行添加或刪除元素的操作。
  • 多個(gè) goroutine 同時(shí)對同一文件進(jìn)行讀寫操作。例如,多個(gè) goroutine 同時(shí)向同一個(gè)文件中寫入數(shù)據(jù)。
  • 多個(gè) goroutine 同時(shí)對同一網(wǎng)絡(luò)連接進(jìn)行讀寫操作。例如,多個(gè) goroutine 同時(shí)向同一個(gè) TCP 連接中寫入數(shù)據(jù)。
  • 多個(gè) goroutine 同時(shí)對同一通道進(jìn)行讀寫操作。例如,多個(gè) goroutine 同時(shí)向同一個(gè)無緩沖通道中發(fā)送數(shù)據(jù)或接收數(shù)據(jù)。

所以,我們要明白的一點(diǎn)是:只要多個(gè) goroutine 并發(fā)訪問了共享資源,就有可能出現(xiàn)競態(tài)條件和數(shù)據(jù)競爭。

避坑辦法

現(xiàn)在,我們已經(jīng)知道了。在編寫并發(fā)程序時(shí),如果不謹(jǐn)慎,沒有考慮清楚共享資源的訪問方式和同步機(jī)制,那么就會(huì)發(fā)生競態(tài)條件和數(shù)據(jù)競爭這些問題,那么如何避免踩坑?避免發(fā)生競態(tài)條件和數(shù)據(jù)競爭的辦法有哪些?請看下面:

  • 互斥鎖:使用 sync 包中的 Mutex 或者 RWMutex,通過對共享資源加鎖來保證同一時(shí)間只有一個(gè) goroutine 訪問。
  • 讀寫鎖:使用 sync 包中的 RWMutex,通過讀寫鎖的機(jī)制來允許多個(gè) goroutine 同時(shí)讀取共享資源,但是只允許一個(gè) goroutine 寫入共享資源。
  • 原子操作:使用 sync/atomic 包中提供的原子操作,可以對共享變量進(jìn)行原子操作,從而保證不會(huì)出現(xiàn)競態(tài)條件和數(shù)據(jù)競爭。
  • 通道:使用 Go 語言中的通道機(jī)制,可以將數(shù)據(jù)通過通道傳遞,從而避免直接對共享資源的訪問。
  • WaitGroup:使用 sync 包中的 WaitGroup,可以等待多個(gè) goroutine 完成后再繼續(xù)執(zhí)行,從而保證多個(gè) goroutine 之間的順序性。
  • Context:使用 context 包中的 Context,可以傳遞上下文信息并控制多個(gè) goroutine 的生命周期,從而避免出現(xiàn)因?yàn)槟硞€(gè) goroutine 阻塞導(dǎo)致整個(gè)程序阻塞的情況。

實(shí)戰(zhàn)場景

1.互斥鎖

比如在一個(gè)Web服務(wù)器中,多個(gè)goroutine需要同時(shí)訪問同一個(gè)全局計(jì)數(shù)器的變量,達(dá)到記錄網(wǎng)站訪問量的目的。

在這種情況下,如果沒有對訪問計(jì)數(shù)器的訪問進(jìn)行同步和保護(hù),就會(huì)出現(xiàn)競態(tài)條件和數(shù)據(jù)競爭的問題。假設(shè)有兩個(gè)goroutine A和B,它們同時(shí)讀取計(jì)數(shù)器變量的值為N,然后都增加了1并把結(jié)果寫回計(jì)數(shù)器,那么最終的計(jì)數(shù)器值只會(huì)增加1而不是2,這就是一個(gè)競態(tài)條件。

為了解決這個(gè)問題,可以使用鎖等機(jī)制來保證訪問計(jì)數(shù)器的同步和互斥。在Go中,可以使用互斥鎖(sync.Mutex)來保護(hù)共享資源。當(dāng)一個(gè)goroutine需要訪問共享資源時(shí),它需要先獲取鎖,然后訪問資源并完成操作,最后釋放鎖。這樣就可以保證每次只有一個(gè)goroutine能夠訪問共享資源,從而避免競態(tài)條件和數(shù)據(jù)競爭問題。

看下面的代碼:

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()
   // 訪問計(jì)數(shù)器并增加值
   count++
   // 釋放鎖
   mutex.Unlock()
   wg.Done()
  }()
 }
 // 等待所有g(shù)oroutine執(zhí)行完畢
 wg.Wait()
 // 輸出計(jì)數(shù)器的最終值
 fmt.Println(count)
}

在上面的代碼中,使用了互斥鎖來保護(hù)計(jì)數(shù)器變量的訪問。每個(gè)goroutine在訪問計(jì)數(shù)器變量之前先獲取鎖,然后進(jìn)行計(jì)數(shù)器的增加操作,最后釋放鎖。這樣就可以保證計(jì)數(shù)器變量的一致性和正確性,避免競態(tài)條件和數(shù)據(jù)競爭問題。

具體的思路是,啟動(dòng)每個(gè) goroutine 時(shí)調(diào)用 wg.Add(1) 來增加等待組的計(jì)數(shù)器。然后,在所有 goroutine 執(zhí)行完畢后,調(diào)用 wg.Wait() 來等待它們完成。最后,輸出計(jì)數(shù)器的最終值。

請注意,這個(gè)假設(shè)的場景和這個(gè)代碼示例,僅僅只是是為了演示如何使用互斥鎖來保護(hù)共享資源,實(shí)際情況可能更加復(fù)雜。例如,在實(shí)際的運(yùn)維開發(fā)中,如果使用鎖的次數(shù)過多,可能會(huì)影響程序的性能。因此,在實(shí)際開發(fā)中,還需要根據(jù)具體情況選擇合適的同步機(jī)制來保證并發(fā)程序的正確性和性能。

2.讀寫鎖

下面是一個(gè)使用 sync 包中的 RWMutex 實(shí)現(xiàn)讀寫鎖的代碼案例:

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) {
 // 寫入共享數(shù)據(jù),獲取寫鎖
 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è)寫入?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è)寫入?yún)f(xié)程,它們都會(huì)訪問一個(gè)共享的變量 count。讀取協(xié)程使用 RLock() 方法獲取讀鎖,寫入?yún)f(xié)程使用 Lock() 方法獲取寫鎖。通過讀寫鎖的機(jī)制,多個(gè)讀取協(xié)程可以同時(shí)讀取共享數(shù)據(jù),而寫入?yún)f(xié)程則會(huì)等待讀取協(xié)程全部結(jié)束后才能執(zhí)行,從而避免了讀取協(xié)程在寫入?yún)f(xié)程執(zhí)行過程中讀取到臟數(shù)據(jù)的問題。

3.原子操作

下面是一個(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é)程對計(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ā)地對計(jì)數(shù)器進(jìn)行增量操作。由于多個(gè)協(xié)程同時(shí)對計(jì)數(shù)器進(jìn)行操作,如果不使用同步機(jī)制,就會(huì)出現(xiàn)競態(tài)條件和數(shù)據(jù)競爭。為了保證程序的正確性和健壯性,使用了 sync/atomic 包中提供的原子操作,通過 AddInt64() 方法對計(jì)數(shù)器進(jìn)行原子加操作,保證了計(jì)數(shù)器的并發(fā)安全。最后使用 LoadInt64() 方法獲取計(jì)數(shù)器的值并輸出。

4.通道

下面是一個(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é)程對計(jì)數(shù)器進(jìn)行增量操作
    for i := 0; i < 10; i++ {
        go func() {
            for j := 0; j < 10; j++ {
                // 將增量操作發(fā)送到通道中
                ch <- 1
            }
            // 任務(wù)完成,向等待組發(fā)送信號
            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ā)地對計(jì)數(shù)器進(jìn)行增量操作。為了避免直接對共享資源的訪問,使用了一個(gè)容量為 10 的有緩沖通道,將增量操作通過通道傳遞,然后在主協(xié)程中從通道中接收增量操作并累加到計(jì)數(shù)器中。在協(xié)程中使用了等待組等待所有協(xié)程完成任務(wù),保證了程序的正確性和健壯性。最后輸出計(jì)數(shù)器的值。

5.WaitGroup

下面是一個(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"。

6.Context

下面是一個(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() 信號就結(jié)束任務(wù)并退出循環(huán),否則就打印出正在運(yùn)行的信息并等待一段時(shí)間。在 main 函數(shù)中,通過調(diào)用 cancel() 來發(fā)送一個(gè)信號,通知所有 Goroutine 結(jié)束任務(wù)。使用 sync.WaitGroup 等待所有 Goroutine 結(jié)束任務(wù),然后輸出 "All workers have stopped"。

到此這篇關(guān)于詳解Go并發(fā)編程時(shí)如何避免發(fā)生競態(tài)條件和數(shù)據(jù)競爭的文章就介紹到這了,更多相關(guān)Go并發(fā)編程內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go讀寫鎖操作方法示例詳解

    Go讀寫鎖操作方法示例詳解

    這篇文章主要為大家介紹了Go讀寫鎖方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • GO語言原生實(shí)現(xiàn)文件上傳功能

    GO語言原生實(shí)現(xiàn)文件上傳功能

    這篇文章主要為大家詳細(xì)介紹了GO語言原生實(shí)現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • 使用Golang實(shí)現(xiàn)加權(quán)負(fù)載均衡算法的實(shí)現(xiàn)代碼

    使用Golang實(shí)現(xiàn)加權(quán)負(fù)載均衡算法的實(shí)現(xiàn)代碼

    這篇文章主要介紹了使用Golang實(shí)現(xiàn)加權(quán)負(fù)載均衡算法的實(shí)現(xiàn)代碼,詳細(xì)說明權(quán)重轉(zhuǎn)發(fā)算法的實(shí)現(xiàn),通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-09-09
  • go編譯標(biāo)簽build?tag注釋里語法詳解

    go編譯標(biāo)簽build?tag注釋里語法詳解

    這篇文章主要為大家介紹了go編譯標(biāo)簽build?tag注釋里語法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 在Golang中正確的修改HTTPRequest的Host的操作方法

    在Golang中正確的修改HTTPRequest的Host的操作方法

    我們工作中經(jīng)常需要通過HTTP請求Server的服務(wù),比如腳本批量請求接口跑數(shù)據(jù),由于一些網(wǎng)關(guān)策略,部分Server會(huì)要求請求中Header里面附帶Host參數(shù),所以本文給大家介紹了如何在Golang中正確的修改HTTPRequest的Host,需要的朋友可以參考下
    2023-12-12
  • Go語言擴(kuò)展原語之ErrGroup的用法詳解

    Go語言擴(kuò)展原語之ErrGroup的用法詳解

    除標(biāo)準(zhǔn)庫中提供的同步原語外,Go語言還在子倉庫sync中提供了4種擴(kuò)展原語,本文主要為大家介紹的是其中的golang/sync/errgroup.Group,感興趣的小伙伴可以了解一下
    2023-07-07
  • Golang輕量級IoC容器安裝使用示例

    Golang輕量級IoC容器安裝使用示例

    這篇文章主要為大家介紹了Golang輕量級IoC容器安裝使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Golang實(shí)現(xiàn)Dijkstra算法過程詳解

    Golang實(shí)現(xiàn)Dijkstra算法過程詳解

    Dijkstra 算法是一種用于計(jì)算無向圖的最短路徑的算法,它是基于貪心策略的,每次選擇當(dāng)前距離起始節(jié)點(diǎn)最近的未訪問節(jié)點(diǎn)進(jìn)行訪問,并更新其相鄰節(jié)點(diǎn)的距離值,以得到最短路徑,這篇文章主要介紹了Golang實(shí)現(xiàn)Dijkstra算法,需要的朋友可以參考下
    2023-05-05
  • 從零封裝Gin框架配置初始化全局變量

    從零封裝Gin框架配置初始化全局變量

    這篇文章主要為大家介紹了從零封裝Gin框架配置初始化全局變量,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • go中如何獲取本機(jī)ip地址

    go中如何獲取本機(jī)ip地址

    這篇文章主要介紹了go中如何獲取本機(jī)ip地址問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09

最新評論