Go 臨界資源的安全問題(引入同步非同步處理)

LiberHome發表於2022-05-12

臨界資源定義

併發環境中多個程式、執行緒、協程共享的資源

臨界資源的特點

可能會因為併發操作導致資料出現不一致性,舉個例子,下面程式碼中的a及時臨界資源

package main

import (
    "fmt"
    "time"
)

func main() {
    //臨界資源
    a := 1
    go func() {
        a = 2
        fmt.Println("in this goroutine: a is : ", a)
    }()
    //在主goroutine中
    a = 3
    time.Sleep(1)
    fmt.Println("in the main goroutine: a is : ", a)
}

    經典的售票問題,好多個視窗同時售票的門票的數量就是一個典型的臨界資源安全問題,下面用4個協程模擬一下售票過程:

package main

import (
    "fmt"
    "math/rand"
    "time"
)

var ticket = 10 //the amount of the total ticket is 100
func main() {
    //這裡啟動4個goroutine模擬4個售票口 同時售票
    go saleTickets("ticket window1")
    go saleTickets("ticket window2")
    go saleTickets("ticket window3")
    go saleTickets("ticket window4")
    //這裡為了保證 主協程 最後執行完 先用sleep (當然,也可以用同步等待組、chanel實現)
    time.Sleep(10 * time.Second)
}

func saleTickets(name string) {
    rand.Seed(time.Now().UnixNano())
    for {
        if ticket > 0 {
            time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond)
            fmt.Println(name, "saled: ", ticket)
            ticket--
        } else {
            fmt.Println(name, "sorry tickets are sold out")
            break
        }
    }
}

執行結果如下:

ticket window3 saled:  10
ticket window1 saled:  9
ticket window4 saled:  8
ticket window2 saled:  7
ticket window3 saled:  6
ticket window2 saled:  5
ticket window1 saled:  4
ticket window1 saled:  3
ticket window4 saled:  2
ticket window2 saled:  1
ticket window2 sorry tickets are sold out
ticket window4 saled:  0
ticket window4 sorry tickets are sold out
ticket window3 saled:  -1
ticket window3 sorry tickets are sold out
ticket window1 saled:  -2
ticket window1 sorry tickets are sold out

這裡居然賣出了-2張票,這明顯除了問題,問題出在哪裡呢?

  • 我們假設現在只剩下最後1張票了,現在程式在主協程裡面
  • 視窗4的協程拿到cpu資源,讀取了剩餘ticket總數為1,然後sleep,釋放cpu資源
  • 視窗3的協程拿到cpu資源,發現剩餘ticket總數為1(因為視窗4的協程進入sleep了,並沒有在視窗3拿到cpu資源之前對ticket進行修改),然後sleep,釋放cpu資源。
  • 視窗4醒了,ticket = 1 - 1 = 0
  • 視窗3醒了,ticket = 0 - 1 = -1
    針對這種問題,可以通過上鎖,在某一時間段只允許一個goroutine來訪問這個共享資料,訪問完畢,解鎖之後,其他goroutine才能訪問的方式解決。
        不過有意思的是,go並不鼓勵這樣以共享的方式去通訊,而是以通訊的方式去共享【也就是不鼓勵用sync包上鎖,鼓勵使用chanel】

資源參考:bilibili

相關文章