Golang tutorial implementation of the non reentrant function
- 2020-06-19 10:31:15
- OfStack
Function function
The Go function does not support nesting, overloading, and default arguments
However, the following features are supported:
preface
A non-reentrant function is a function that can be executed only once at any point in time, no matter how many times it is called and how many goroutines there are.
This article explains blocking non-reentrant functions and producing non-reentrant function implementations in golang.
Scenario use case
A service polls some conditions, monitoring 1 status per second. We want each state to be checked independently without blocking. The implementation might look like this:
func main() {
tick := time.Tick(time.Second)
go func() {
for range tick {
go CheckSomeStatus()
go CheckAnotherStatus()
}
}()
}
We chose to run each status check in our goroutine so that
CheckAnotherStatus()
Don't wait for
CheckSomeStatus()
To complete.
Each check usually takes a very short time, and much less than one second. But if
CheckAnotherStatus()
It takes more than a second to run itself. What happens? There may be an unexpected network or disk delay that affects the execution time of the check.
Does it make sense to execute a function twice at the same time? If not, we want it to be non-reentrant.
Blocked, non - reentrant function
An easy way to prevent functions from running multiple times is to use
sync.Mutex
.
Assuming we only care about calling the function from the above loop, we can implement the lock from outside the function:
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()
}()
}
}()
}
The above code guarantees that
CheckAnotherStatus()
Not performed by multiple iterations of the loop. Executed before
CheckAnotherStatus()
Any subsequent iteration of the loop will be blocked by the mutex.
The blocking solution has the following properties:
It ensures a lot of"CheckAnotherStatus()
"As the number of iterations of the loop.
Suppose 1 execution ".
CheckAnotherStatus()
"And subsequent iterations will result in requests to call the same function.
Yield, non-reentrant function
In our status check story, it might not make sense for the next 10 calls to pile up. 1 is stagnant
CheckAnotherStatus()
The execution is complete, all 10 calls are executed suddenly, sequentially, and possibly in the next 1 second, 10 identical checks in the same 1 second.
The other solution is to give in. One profitable solution is:
If it has been implemented,"CheckAnotherStatus()
Suspension of execution.
Will run up to 1 time"
CheckAnotherStatus()
".
Compared to the number of loop iterations, the actual"
CheckAnotherStatus()
Fewer calls.
The solution is implemented in the following ways:
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)
Only in the
reentranceFlag==0
true is returned with an atomic setting of 1. In this case, entry is allowed, and the function can be executed. reentranceFlag stays at 1 until
CheckAnotherStatus()
Done, it is reset. when
CompareAndSwapInt64(...)
When false is returned, this means that
reentranceFlag!=0
, which means that the function has been executed by another goroutine. The code generates and silently exits the function.
conclusion
We chose to implement non-reentrant code outside of the function in question; We can implement it in the function itself. In addition, int32 is also sufficient for int64. Above is the content of this piece, we have what question can communicate in the message below the article.