What is a reentrant lock
When a thread acquires a lock, it acquires the lock if no other thread owns it. Later, if another thread requests the lock again, it will be in a blocking wait state. However, if the thread that owns the lock requests the lock again, it does not block but returns successfully, so it is called a reentrant lock. As long as you own the lock, you can call it as hard as you want, for example by implementing some algorithm recursively, without the caller blocking or deadlocking.
Mutex is not a reentrant lock. Mutex’s implementation has no record of which Goroutine owns the lock. In theory, any Goroutine can Unlock the lock at will, so there is no way to calculate the reentrant condition.
Implementation scheme
There are two solutions to reentrant locking
- Hacker gets the Goroutine ID and records the goroutine ID for the lock, which implements the Locker interface.
- When a Lock/Unlock method is called, goroutine provides a token to identify itself, rather than the Goroutine ID obtained through hacker, which is not enough for the Locker interface.
Plan a
To obtain the GORoutine ID, run runtime.Stack to obtain the current goroutine id. Conveniently, there are already open source packages that implement goID
RecursiveMutex struct {sync.Mutex owner int64 RecursiveMutex {// Goroutine ID recursion Int32 // RecursiveMutex Lock() {gid := goid.get () // If the goroutine currently holding the Lock is the goroutine of this call, it is reentrant if atomy.loadint64 (&m.owner) == gid {m.recursion++ return} m.mutex.lock () // The first call to the goroutine that gets the lock, Make a note of its Goroutine ID, the number of calls plus 1 atomic.storeInt64 (&m.owner, Gid) m RecursiveMutex = 1} func (m *RecursiveMutex) Unlock() {gid := goid.get () Error using if atomic.loadInt64 (&m.wner)! = gid { panic(fmt.Sprintf("wrong the owner(%d): %d!" , m.wner, gid))} // The number of calls minus 1 m.sion -- if m.sion! = 0 {// If the goroutine has not been completely freed, return} // The last time this goroutine was called, Need to release the lock atomy.storeInt64 (&m.owner, -1) m.mutex.unlock ()}Copy the code
Although the owner can call the Lock multiple times, the Unlock must be called the same number of times to release the Lock.
Scheme 2: Token
Scheme 1 uses the Goroutine ID to identify the Goroutine. At the same time, the caller can also provide a token, which is passed in when the lock is acquired and the token is passed in when the lock is released. The goroutine ID in scheme 1 is replaced by the token passed in by the user. The other logic is the same as that in Scheme 1.
Type TokenRecursiveMutex struct {sync.Mutex Token int64 recursion int32} TokenRecursiveMutex Lock(token int64) {if atomic.LoadInt64(&m.token) == token { // If the token passed is the same as the token that holds the Lock, it is a recursive call to m.recursionsion+ + return} m.mutex.lock (). // Record the token atomic.storeInt64 (&m.token, TokenRecursiveMutex: Unlock(token int64) {if atomic.LoadInt64(&m.token)! Sprintf("wrong the owner(%d): %d!"); , m.token, token))} m.sion -- // The token that currently holds the lock releases the lock if m.sion! = 0 {return} atomy.storeint64 (&m.token, 0) // There is no recursive call, release the lock m.mutex.unlock ()}Copy the code