This is a creation in Article, where the information may have evolved or changed.
Catalogue [−]
- Use unsafe to manipulate pointers
- Implement spin Lock
- Using the channel to achieve
- Performance comparison
- Resources
Go standard library sync/Mutex
, RWMutex
implements the sync/Locker
interface, provides the Lock()
UnLock()
method, can acquire the lock and the release lock, we can use it conveniently to control our concurrency control to the shared resource.
However, when a lock in the standard library is Mutex.Lock
obtained, it will be blocked if it is not released before it is freed Lock
, and this design may not meet my needs in some cases. Sometimes we want to try to get the lock, if we get it, no problem to continue, if we can't get it, we don't want to block it, but we want to call the other logic, this time we need the TryLock
method.
Although it was early (13) to ask for the Go Development Group, the request was not included in the official repository and eventually closed in the official repository, which is not currently being added by the official repository.
sync/Mutex
by the way, the source code implementation can be accessed here, it should be implemented a spin (spin) plus sleep way to achieve, interested readers can read the source code, or read related articles, such as Go Mutex source analysis. This is not the content of this article, readers can find some information to read.
Well, turn to the point and see how several implementations TryLock
are made.
Using the unsafe
action pointer
If you look at sync/Mutex
the code, you will find Mutext
the data structure as follows:
1234 |
type struct int32Sema uint32} |
It uses state
this 32-bit integer to mark the lock's occupancy, so we can use it CAS
to try to acquire the lock.
The code is implemented as follows:
123456789 |
const mutexlocked = 1iotatypestruct {sync. Mutex}funcbool {return atomic.compareandswapint32 (*int32) (unsafe. Pointer (&m.mutex)), 0, mutexlocked)} |
Used in the same way as standard libraries Mutex
.
12345678910111213141516171819 td> |
func main () {var m Mutexm.lock ( ) go func () {M.lock ()} () time. Sleep (time. Second) fmt. Printf ( "Trylock:%t\n" , M.trylock ()) //false fmt. Printf ( "Trylock:%t\n" , M.trylock ()) //false M.unlock () Fmt. Printf ( "Trylock:%t\n" , M.trylock ()) //true fmt. Printf ( "Trylock:%t\n" , M.trylock ()) //false m.unlock () Fmt. Printf ( "Trylock:%t\n" , M.trylock ()) //true m.unlock ()} |
Note that TryLock
instead of checking the status of the lock, it attempts to acquire the lock, so TryLock
when it returns True, the lock is actually acquired.
Implement spin Lock
The above section gives us the inspiration to use uint32
and CAS
manipulate us to a custom lock:
1234567891011121314151617 |
type struct UInt32} func (SL *spinlock) Lock () { for!SL. Trylock () {runtime. Gosched ()}}func (SL *spinlock) Unlock () {Atomic. StoreUint32 (&SL.F, 0)}funcbool {return atomic. CompareAndSwapUint32 (&SL.F, 0, 1)} |
Overall, it seems to be a lite version of the standard library, with no sleep and wake-up features.
Of course, this spin lock can be high in the case of large concurrency, because its Lock
method uses a spin, and if someone does not release the lock, the loop will be executed at a faster speed but with high CPU usage.
Of course, this version can be further optimized, especially at the time of copying. The following is an optimized version:
1234567891011121314151617181920 |
type spinLock uint32 func (SL *spinlock) Lock () {for !atomic. CompareAndSwapUint32 ((*uint32 ) (SL), 0 , 1 ) {runtime. Gosched () //without This it locks up on Gomaxprocs > 1 }}func (SL *spinlock) Unlock () {Atomic. StoreUint32 ((*uint32 ) (SL), 0 )} Func (SL *spinlock) Trylock () bool {return atomic. CompareAndSwapUint32 ((*uint32 ) (SL), 0 , 1 )} func SpinLock () sync. Locker {var lock spinlockreturn &lock}
|
Using the channel to achieve
Another way is to use the channel:
12345678910111213141516171819202122232425 |
typeChanmutexChan struct{}func(M *chanmutex) Lock () {ch: = (Chan struct{}) (*M) Ch <-struct{}{}}func(M *chanmutex) Unlock () {ch: = (Chan struct{}) (*m)Select{ Case<-ch:default:Panic("Unlock of unlocked mutex")}}func(M *chanmutex) Trylock ()BOOL{ch: = (Chan struct{}) (*m)Select{ CaseCH <-struct{}{}:return truedefault:}return false} |
Interested students can pay attention to the library Lrita/gosync written by my colleague.
Performance comparison
First look at the Mutex
RWMutex
Lock
performance Comparisons in the above three ways and in the standard library Unlock
:
12345 |
Benchmarkmutex_lockunlock-4 100000000 16.8Ns/op0B/op0Allocs/opbenchmarkrwmutex_lockunlock-4 50000000 36.8Ns/op0B/op0Allocs/opbenchmarkunsafemutex_lockunlock-4 100000000 16.8Ns/op0B/op0Allocs/opbenchmarkchannmutex_lockunlock-4 20000000 65.6Ns/op0B/op0Allocs/opbenchmarkspinlock_lockunlock-4 100000000 18.6Ns/op0B/op0Allocs/op |
can see single-threaded (goroutine) in the case of ' spinlock ' is not much better than the standard library, but almost, the situation of concurrent testing is better, as shown in the table below, this is expected.
unsafe
The same way as the standard library.
channel
The performance of the method is relatively poor.
12345 |
Benchmarkmutex_lockunlock_c-4 20000000 75.3Ns/op0B/op0Allocs/opbenchmarkrwmutex_lockunlock_c-4 20000000 -Ns/op0B/op0Allocs/opbenchmarkunsafemutex_lockunlock_c-4 20000000 75.3Ns/op0B/op0Allocs/opbenchmarkchannmutex_lockunlock_c-4 10000000 231Ns/op0B/op0Allocs/opbenchmarkspinlock_lockunlock_c-4 50000000 32.3Ns/op0B/op0Allocs/op |
Then look at the TryLock
performance of the lock for three implementations:
123 |
Benchmarkunsafemutex_trylock-4 50000000 34.0 ns/op 0 b/op 0 Allocs/opbenchmarkchannmutex_trylock-4 20000000 83.8 ns/ Op 0 b/op 0 allocs/opbenchmarkspinlock_trylock-4 50000000 30.9 ns/op 0 b/op 0 allocs/op |
Resources
This article refers to the following articles and open source projects:
- https://github.com/golang/go/issues/6123
- https://github.com/LK4D4/trylock/blob/master/ Trylock.go
- https://github.com/OneOfOne/go-utils/blob/master/sync/spinlock.go
- /http Codereview.stackexchange.com/questions/60332/is-my-spin-lock-implementation-correct
- https://github.com /lrita/gosync