筆者原來是使用C++開發的,一直以爲互斥鎖時要是進程間通信中信號量的一個特例。在使用golang開發的時候,一直都很謹慎使用互斥鎖,能用原子變量實現的就用原子變量實現。直到今天,測試了下golang標準庫中sync.Mutex的性能以後,徹底的感覺到自己以前的無知和自以爲是。經過筆者測試,兩者的性能幾乎沒差異,有差異估計就是一點點封裝消耗的性能成本。查看標準包中sync.Mutex實現的源碼發現是以原子變量爲基礎實現的
測試代碼如下:
type Number struct {
number int64
atomicmutx int64
mutx sync.Mutex
}
func AddNumberMutex() {
testlock.mutx.Lock()
testlock.number = testlock.number + 1
testlock.mutx.Unlock()
}
func AddNumberAtomic() {
for {
if atomic.CompareAndSwapInt64(&testlock.number, 0, 1) {
testlock.number = testlock.number + 1
atomic.StoreInt64(&testlock.number, 0)
break
}
}
}
func BenchmarkMutex(b *testing.B) {
for i := 0; i < b.N; i++ {
AddNumberMutex()
}
}
func BenchmarkAtomic(b *testing.B) {
atomic.StoreInt64(&testlock.number, 0)
for i := 0; i < b.N; i++ {
AddNumberAtomic()
}
}
運行結果如下:
goos: linux
goarch: amd64
BenchmarkMutex 100000000 20.2 ns/op
BenchmarkAtomic 100000000 20.9 ns/op
併發測試代碼:
func BenchmarkMutexParallel(b *testing.B) {
atomic.StoreInt64(&testlock.number, 0)
b.RunParallel(func(pb *testing.PB){
for pb.Next() {
AddNumberMutex()
}
})
}
func BenchmarkAtomicParallel(b *testing.B) {
atomic.StoreInt64(&testlock.number, 0)
b.RunParallel(func(pb *testing.PB){
for pb.Next() {
AddNumberAtomic()
}
})
}
併發測試結果:
BenchmarkMutexParallel 100000000 24.3 ns/op
BenchmarkAtomicParallel 100000000 21.2 ns/op
查看Mutex.Lock()接口代碼如下,這裏不做代碼說明,但是確實一看就可以知道是以原子變量爲基礎實現的:
// Lock locks m.
// If the lock is already in use, the calling goroutine
// blocks until the mutex is available.
func (m *Mutex) Lock() {
// Fast path: grab unlocked mutex.
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
var waitStartTime int64
starving := false
awoke := false
iter := 0
old := m.state
for {
// Don't spin in starvation mode, ownership is handed off to waiters
// so we won't be able to acquire the mutex anyway.
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
// Active spinning makes sense.
// Try to set mutexWoken flag to inform Unlock
// to not wake other blocked goroutines.
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
awoke = true
}
runtime_doSpin()
iter++
old = m.state
continue
}
new := old
// Don't try to acquire starving mutex, new arriving goroutines must queue.
if old&mutexStarving == 0 {
new |= mutexLocked
}
if old&(mutexLocked|mutexStarving) != 0 {
new += 1 << mutexWaiterShift
}
// The current goroutine switches mutex to starvation mode.
// But if the mutex is currently unlocked, don't do the switch.
// Unlock expects that starving mutex has waiters, which will not
// be true in this case.
if starving && old&mutexLocked != 0 {
new |= mutexStarving
}
if awoke {
// The goroutine has been woken from sleep,
// so we need to reset the flag in either case.
if new&mutexWoken == 0 {
panic("sync: inconsistent mutex state")
}
new &^= mutexWoken
}
if atomic.CompareAndSwapInt32(&m.state, old, new) {
if old&(mutexLocked|mutexStarving) == 0 {
break // locked the mutex with CAS
}
// If we were already waiting before, queue at the front of the queue.
queueLifo := waitStartTime != 0
if waitStartTime == 0 {
waitStartTime = runtime_nanotime()
}
runtime_SemacquireMutex(&m.sema, queueLifo)
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
old = m.state
if old&mutexStarving != 0 {
// If this goroutine was woken and mutex is in starvation mode,
// ownership was handed off to us but mutex is in somewhat
// inconsistent state: mutexLocked is not set and we are still
// accounted as waiter. Fix that.
if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
panic("sync: inconsistent mutex state")
}
delta := int32(mutexLocked - 1<<mutexWaiterShift)
if !starving || old>>mutexWaiterShift == 1 {
// Exit starvation mode.
// Critical to do it here and consider wait time.
// Starvation mode is so inefficient, that two goroutines
// can go lock-step infinitely once they switch mutex
// to starvation mode.
delta -= mutexStarving
}
atomic.AddInt32(&m.state, delta)
break
}
awoke = true
iter = 0
} else {
old = m.state
}
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
}