首頁  >  文章  >  後端開發  >  一文聊聊Go語言中資源競爭問題

一文聊聊Go語言中資源競爭問題

青灯夜游
青灯夜游轉載
2023-02-17 14:40:203226瀏覽

一文聊聊Go語言中資源競爭問題

我們都知道,在並發程式設計中,執行緒安全性是非常重要的。接下來我們就假定一個場景,複現一下線程不安全的情況,再聊聊如何在Go中解決

場景

我們現在需要對1~ 100求他們的階乘,並將結果放到一個map中

1! = 1 = 1
2! = 1 * 2 = 2
3! = 1 * 2 * 3 = 6
4! = 1 * 2 * 3 * 4 = 24
5! = 1 * 2 * 3 * 4 * 5 = 120
...
{
    1: 1
    2: 2
    3: 6
    4: 24
    5: 120
    ...
}

程式碼實作

var factorialMap = make(map[int]int)

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    factorialMap[n] = result
}

func main() {
    for i := 1; i < 10; i++ {
        Factorial(i)
    }
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

一文聊聊Go語言中資源競爭問題上述程式碼執行結果其實是沒問題的,為什麼會出現亂序呢?因為這是go語言中map其實就是亂序的,按照我們的理解,先存的先出,但是不好意思,Golang的map不是這樣的。 上面執行也沒什麼問題啊,細心的同學可能發現了,這個版本的程式碼並沒有用上並發,對吧。好接下來我們繼續改進

並發實作

var factorialMap = make(map[int]int)

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    factorialMap[n] = result
}

func main() {
    for i := 1; i < 10; i++ {
        go Factorial(i)
    }
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

一文聊聊Go語言中資源競爭問題#我們可以發現,並發版就是在呼叫運算階乘函數的前面加上了一個go而已。不要小看這個go,扯遠了,當然大家知道這是go語言中開啟一個協程的關鍵字即可。

執行結果就是,控制台啥都沒輸出,這是因為主協程和子協程之間的執行關係,下面我們畫圖理解

##從上圖中我們可以發現,主協程執行的時間短(表現在比較短),子協程執行時間比較長(表現在比較長) 我們一定要記住,子協程是相對於目前的主協程來說的,如果主協程不存在了,那就沒有子協程了一文聊聊Go語言中資源競爭問題

所以上面程式碼啥都沒輸出就是因為,主協程已經執行完了,但是子協程還沒做完,那子協程都沒做完,

factorialMap中能有東西嗎?

主等子

這就引出我們第一個問題,主協程如何等待子協程執行完再退出程式。我們現在用一個最簡單,最容易想到的做法

var factorialMap = make(map[int]int)

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    factorialMap[n] = result
}

func main() {
    for i := 1; i < 100; i++ {
        go Factorial(i)
    }
    time.Sleep(time.Second * 3)
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

當並發數比較小的時候,這個問題可能不會出現,一旦並發數變大,問題就立刻出現了一文聊聊Go語言中資源競爭問題

圖中的執行結果是

並發map寫入錯誤為什麼會出現這個問題,我們假設100個人往一個籃子裡放水果,很容易。但是100個人從一個籃子裡拿水果,那就會出問題,首先,籃子裡的水果不一定夠100個,其二每個人都想先拿,必然會引起爭搶。

問題一最佳化

針對上面的問題,我們引入全域鎖定的概念。這就有點像我們上廁所,100個人都想上廁所,但廁所只有1個,誰先搶到了誰先上,並且這個人還有給廁所上鎖,防止其他人進來

一文聊聊Go語言中資源競爭問題

var factorialMap = make(map[int]int)
var lock sync.Mutex

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
            result *= i
    }
    // defer 不好理解
    // defer func(){
    // 	lock.Unlock() // 执行完解锁
    // }()
    lock.Lock() // 执行时上锁
    factorialMap[n] = result
    lock.Unlock() // 执行后解锁
}

func main() {
    for i := 1; i < 100; i++ {
        go Factorial(i)
    }
    time.Sleep(time.Second * 3)
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

執行結果有0可能是資料型別存不下了導致的,這個大家不用關心一文聊聊Go語言中資源競爭問題

這樣我們就解決了資源競爭的問題了。但其實還有一個問題,就是我們在主協程中還是必須手動等待,這要非常不好,那如果子協程3秒內解決不了怎麼辦? 一文聊聊Go語言中資源競爭問題

問題二優化

這個問題是我們不想在主協程中手動等待子協程,換句話說是我們不想直接在程式碼中寫明要等待多久

這裡我們就引入了

WaitGroup

var factorialMap = make(map[int]int)
var lock sync.Mutex
var wg sync.WaitGroup

func Factorial(n int) {
    result := 1
    for i := 1; i <= n; i++ {
        result *= i
    }
    lock.Lock() // 执行时上锁
    factorialMap[n] = result
    lock.Unlock() // 执行后解锁
    wg.Done()
}

func main() {
    for i := 1; i < 100; i++ {
        wg.Add(1)
        go Factorial(i)
    }
    wg.Wait()
    for k, v := range factorialMap {
        fmt.Printf("%d 的阶乘是%d\n", k, v)
    }
}

WaitGroup的內部原理大家自己細扣,我這就不講了 總結來說就是

WaitGroup是一個籃子,每開一個協程,就往籃子中加一個標識(Add函數),每執行完一個協程,就從籃子中減去一個標識(Done函數),最後查看籃子中,如果是空的,就表示協程執行完了(Wait函數)

#【推薦學習:

go影片教學

以上是一文聊聊Go語言中資源競爭問題的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除