背景
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性能更高
测试结果截图
测试代码
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)
}