goroutine锁性能分析

背景

goroutine是go语言并发利器,但是假如有goroutine A 和goroutine B需要同步执行某段程序,没有有效的措施,利用sync.Mutex加锁可以实现。但是sync.Mutex是锁线程的,不是锁协程,可能多个协程都在同一个线程下执行逻辑,协程A获取sync.Mutex锁后,这个线程的其他协程就要切换到别的线程执行逻辑了(go的底层做了处理,这些都是猜测的),这就会带来一定的额外开销。因此,实现了一个goroutine级别的锁,经过对比原生的sync.Mutex性能,提示至1.5倍-5倍的性能

goroutine锁原理

核心代码如下

type RoutineLock struct {
	reqs chan chan bool  //请求锁channel
	unlockChan chan bool //解锁channel
	exit chan bool //退出channel
}

每个goroutine有一个RoutineLock (可以看成是goroutine级别的锁)。假如有goroutine A 和goroutine B需要同步处理某个消息,首先获取A和B的锁权限,这个锁是goroutine级别的锁,处理完毕后,释放A和B的锁

性能对比

taskNum:表示有几个goroutine在处理
countPerTask:表示每个goroutine下,有多少个子goroutine在竞争(sync.Mutex或RoutineLock )

测试条件\锁类型 sync.Mutex用时 RoutineLock用时
taskNum=50000; countPerTask=500 102.44s 33.93s
taskNum=1000; countPerTask=500 1.80s 1.25s
taskNum=2000; countPerTask=500 3.42s 2.59s
taskNum=3000; countPerTask=500 30.43s 10.10s
taskNum=4000; countPerTask=500 47.41s 14.08s
taskNum=3000; countPerTask=1000 50.50s 19.31s
taskNum=200; countPerTask=1000 0.80s 0.53s
taskNum=1000; countPerTask=1000 3.50s 2.58s
taskNum=1000; countPerTask=2000 81.87s 13.22s

通过测试结果可以看出,RoutineLock性能更高

测试结果截图

  1. 在这里插入图片描述
  2. goroutine锁性能分析_第1张图片
  3. goroutine锁性能分析_第2张图片
  4. goroutine锁性能分析_第3张图片
  5. goroutine锁性能分析_第4张图片
  6. goroutine锁性能分析_第5张图片
  7. goroutine锁性能分析_第6张图片
  8. goroutine锁性能分析_第7张图片
  9. goroutine锁性能分析_第8张图片

测试代码


var taskNum = 1000
var countPerTask = 2000
var sleepms = time.Duration(0)

type ThreadLock struct {
	sync.Mutex
}
var threadLocks []*ThreadLock
var routineLocks []*routine.BLock
var nrs []*NewRoutineLock

func init()  {
	threadLocks = make([]*ThreadLock, taskNum)
	for i := 0; i < len(threadLocks); i++ {
		threadLocks[i] = &ThreadLock{}
	}


	routineLocks = make([]*routine.BLock, taskNum)
	for i := 0; i < len(routineLocks); i++ {
		routineLocks[i] = routine.NewRoutineLock()
	}
	nrs = make([]*NewRoutineLock, taskNum)
	for i := 0; i < len(nrs); i++ {
		nrs[i] = NewNewRoutineLock(countPerTask)
	}
}
func TestThreadLockPerformance()  {
	fmt.Printf("taskNum=%v,countPerTask=%v\n", taskNum, countPerTask)
	totalWait := routine.NewAwaitWithoutTimeout()
	value := int32(0)
	totalCount := int32(0)
	for i := 0; i < len(threadLocks); i++ {
		lock := threadLocks[i]
		//lockIndex := i
		go func() {
			count := 0
			tempWait := routine.NewAwaitWithoutTimeout()
			for j := 1; j <= countPerTask; j++ {
				//taskIndex := j
				go func() {
					lock.Lock()
					count++
					if sleepms > 0 {
						time.Sleep(time.Millisecond * sleepms)
					}
					atomic.AddInt32(&value, costTimeOp())
					if count >= countPerTask {
						//fmt.Println("complete", lockIndex)
						tempWait.Complete()
					}
					lock.Unlock()
				}()
			}
			tempWait.Wait()

			if atomic.AddInt32(&totalCount, 1) >= int32(taskNum) {
				totalWait.Complete()
			}
		}()
	}
	totalWait.Wait()
	fmt.Println("value=", value)
}
func TestNewRoutineLockPerformance()  {
	totalWait := routine.NewAwaitWithoutTimeout()
	value := int32(0)
	totalCount := int32(0)
	for i := 0; i < len(nrs); i++ {
		//lock := routineLocks[i]
		routineIndex := i
		lock := nrs[routineIndex].main
		subLocks := nrs[routineIndex].locks
		go lock.Run()
		//lockIndex := i
		go func() {
			count := 0
			tempWait := routine.NewAwaitWithoutTimeout()
			for j := 1; j <= countPerTask; j++ {
				taskIndex := j
				go func() {
					lock.LockWithoutTimeout(subLocks[taskIndex-1])
					count++
					//fmt.Println("routineIndex=", routineIndex, ",taskIndex=", taskIndex)
					if sleepms > 0 {
						time.Sleep(time.Millisecond * sleepms)
					}
					atomic.AddInt32(&value, costTimeOp())
					lock.Unlock(subLocks[taskIndex-1])
					if count >= countPerTask {
						//fmt.Println("complete", lockIndex)
						tempWait.Complete()
					}
				}()
			}
			tempWait.Wait()

			if atomic.AddInt32(&totalCount, 1) >= int32(taskNum) {
				totalWait.Complete()
			}
		}()
	}
	totalWait.Wait()
	for i := 0; i < len(nrs); i++ {
		//lock := routineLocks[i]
		nrs[i].main.Exit()
	}
	fmt.Println("value=", value)
}

你可能感兴趣的:(golang)