Go語言競爭狀態講解

2021-10-02 11:21:22 字數 3144 閱讀 3991

兩個或者多個goroutine 有併發,就有資源競爭,如果兩個或者多個 goroutine 在沒有相互同步的情況下,訪問某個共享的資源,比如同時對該資源進行讀寫時,就會處於相互競爭的狀態,這就是併發中的資源競爭。競爭狀態的存在是讓併發程式變得複雜的地方,十分容易引起潛在的問題。對共享資源的操作必須原子化的,同一時刻只能有乙個goroutine對共享資源進行讀和寫操作。

//演示程式中存在的競爭狀態

package main

import (

"fmt"

"runtime"

"sync"

)var (

//counter 是所有goroutine都要增加其值的變數

counter int

wg      sync.waitgroup)

//main函式是所有go程式的入口

下面的**

package main

import (

"fmt"

"runtime"

"sync"

)var (

count int32

wg    sync.waitgroup)

func

main()

func

inccount()

}這個是乙個競爭的例子,程式多執行幾次,結果可能是2,也可能是3,還可能是4。這個是因為count變數沒有任何同步保護,所以兩個goroutine都是會對其進行讀寫,會到知識已經計算好的結果被覆蓋,以至於產生錯誤的結果。**中的 runtime.gosched() 是讓當前 goroutine 暫停的意思,退回執行佇列,讓其他等待的 goroutine 執行,目的是為了使資源競爭的結果更明顯。

下面我們來分析一下程式的執行過程,將兩個 goroutine 分別假設為 g1 和 g2:

通過上面的分析可以看出,之所以出現上面的問題,是因為兩個 goroutine 相互覆蓋結果。

通過執行結果可以看出 goroutine 8 在** 25 行讀取共享資源value := count,而這時 goroutine 7 在** 28 行修改共享資源count = value,而這兩個 goroutine 都是從 main 函式的 16、17 行通過 go 關鍵字啟動的。

一種修正**、消除競爭狀態的辦法是:使用go語言提供的鎖機制,來鎖住共享資源,從而保證goroutine的同步狀態。

鎖住共享資源

go語言提供了傳統的同步 goroutine 的機制,就是對共享資源加鎖。atomic 和 sync 包裡的一些函式就可以對共享的資源進行加鎖操作。

原子函式

原子函式能夠以很底層的加鎖機制來同步訪問整型變數和指標,示例**如下所示:

package main

import (

"fmt"

"runtime"

"sync"

"sync/atomic"

)var (

counter int64

wg      sync.waitgroup)

func

main()

func

inccounter(id

int)

}上述**中使用了 atmoic 包的 addint64 函式,這個函式會同步整型值的加法,方法是強制同一時刻只能有乙個 gorountie 執行並完成這個加法操作。當 goroutine 試圖去呼叫任何原子函式時,這些 goroutine 都會自動根據所引用的變數做同步處理。另外兩個有用的原子函式是 loadint64 和 storeint64。這兩個函式提供了一種安全地讀和寫乙個整型值的方式。下面是**就使用了 loadint64 和 storeint64 函式來建立乙個同步標誌,這個標誌可以向程式裡多個 goroutine 通知某個特殊狀態。

package main

import (

"fmt"

"sync"

"sync/atomic"

"time"

)var (

shutdown int64

wg       sync.waitgroup)

func

main()

func

dowork(name

string)

} }

上面**中 main 函式使用 storeint64 函式來安全地修改 shutdown 變數的值。如果哪個 dowork goroutine 試圖在 main 函式呼叫 storeint64 的同時呼叫 loadint64 函式,那麼原子函式會將這些呼叫互相同步,保證這些操作都是安全的,不會進入競爭狀態。

互斥鎖

另一種同步訪問共享資源的方式是使用互斥鎖,互斥鎖這個名字來自互斥的概念。互斥鎖用於在**上建立乙個臨界區,保證同一時間只有乙個 goroutine 可以執行這個臨界**。

package main

import (

"fmt"

"runtime"

"sync"

)var (

counter int64

wg      sync.waitgroup

mutex   sync.mutex)

func

main()

func

inccounter(id

int)

mutex.unlock() //釋放鎖,允許其他正在等待的goroutine進入臨界區}

} 輸出:

final counter= 4

同一時刻只有乙個 goroutine 可以進入臨界區。之後直到呼叫 unlock 函式之後,其他 goroutine 才能進去臨界區。當呼叫 runtime.gosched 函式強制將當前 goroutine 退出當前執行緒後,排程器會再次分配這個 goroutine 繼續執行。

Go語言之併發資源競爭

併發本身並不複雜,但是因為有了資源競爭的問題,就使得我們開發出好的併發程式變得複雜起來,因為會引起很多莫名其妙的問題。package main import fmt runtime sync var count int32 wg sync.waitgroup func main func incco...

go語言切片深入講解

package main import fmt func change s int func main change slice fmt.println slice 我們先來看一下結果 我們可以看到切片當作函式引數的時候呼叫之後值確實改變了,這也間接的可以認為切片是位址傳遞,但是我們想要了解的更深入...

日常 Go語言聖經 競爭條件習題

package main import fmt sync var balance int func deposit amount int func balance int 問題 1.在alice執行期間 balance balance amount 這一步運算可能會被bob中間擠占 2.當執行到ba...