golang標準庫中sync.Mutex性能測試

筆者原來是使用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))
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章