一個不可重入的函數就是一個在任什麼時候間點只能執行一次的函數,無論它被調用了多少次,以及有多少goroutines。golang
本篇文章說明了阻塞不可重入函數,並在golang中產生不可重入的函數實現。api
場景用例網絡
某個服務是對某些條件進行輪詢,每秒監視一些狀態。咱們但願每一個狀態均可以獨立地檢查,而不須要阻塞。實現多是這樣的:ide
func main() {
tick := time.Tick(time.Second) go func() { for range tick { go CheckSomeStatus() go CheckAnotherStatus() } }() }
咱們選擇在本身的goroutine中運行每一個狀態檢查,以便 CheckAnotherStatus() 不會等待 CheckSomeStatus() 完成。函數
每一項檢查一般都要花費很短的時間,並且比一秒要少得多。可是,若是 CheckAnotherStatus() 自己須要超過一秒的時間運行,會發生什麼呢?可能會有一個意外的網絡或磁盤延遲影響檢查的執行時間。atom
在同一時間執行兩次的函數是否有意義?若是沒有,咱們但願它是不可重入的。it
阻塞,不可重入函數class
防止函數屢次運行的簡單方法是使用sync.Mutex。import
假設咱們只關心從上面的循環調用這個函數,咱們能夠從函數外面實現鎖:循環
import ( "sync" "time" )
func main() {
tick := time.Tick(time.Second) var mu sync.Mutex go func() { for range tick { go CheckSomeStatus() go func() { mu.Lock() defer mu.Unlock()
CheckAnotherStatus() }() } }() }
上面的代碼保證了 CheckAnotherStatus() 不是由循環的屢次迭代執行的。在之前執行 CheckAnotherStatus() 的時候,循環的任何後續迭代都會被互斥鎖阻塞。
阻塞解決方案具備如下屬性:
它確保了許多「CheckAnotherStatus()」的調用做爲循環迭代的次數。
假設一個執行「CheckAnotherStatus()」的停頓,隨後的迭代會致使請求調用相同函數的請求。
屈服,不可重入函數
在咱們的狀態檢查故事中,對隨後的10個電話堆積起來可能沒有意義。一個停滯不前的 CheckAnotherStatus() 執行完成了,全部10個調用忽然執行,順序,而且可能在接下來的一秒內完成,在同一秒內完成10個相同的檢查。
另外一個解決辦法是屈服。一個有收益的解決方案是:
若是已經執行了「CheckAnotherStatus()」的停止執行。
將最多運行一次「CheckAnotherStatus()」的執行。
與循環迭代的次數相比,實際上可能運行的「CheckAnotherStatus()」的調用更少。
解決方案是經過如下方式實現的:
import (
"sync/atomic" "time"
)
func main() { tick := time.Tick(time.Second)
var reentranceFlag int64 go func() {
for range tick {
go CheckSomeStatus() go func() {
if atomic.CompareAndSwapInt64(&reentranceFlag, 0, 1) { defer atomic.StoreInt64(&reentranceFlag, 0) } else {
return } CheckAnotherStatus() }() } }() }
atomic.compareandswapint64(&reentranceFlag, 0, 1) 只有在 reentranceFlag==0 時纔會返回true,並將原子性地設置爲1。在這種狀況下,容許進入,而且能夠執行該函數。reentranceFlag保持在1,直到 CheckAnotherStatus() 完成,此時它被重置。當 CompareAndSwapInt64(...) 返回false時,這意味着reentranceFlag!=0,這意味着該函數已經由另外一個goroutine執行。代碼產生並靜默地退出函數。
總結
咱們選擇在問題的函數以外實現不可重入的代碼;咱們能夠在函數自己中實現它。另外,對於 int64 而言,int32固然也足夠用。以上就是本篇的內容,你們有什麼疑問能夠在文章下面留言溝通。