golang互斥锁和读写锁性能分析

本文探讨了在并发环境中,Golang的互斥锁和读写锁的使用场景和性能差异。通过实验对比,发现在没有读写冲突的情况下,互斥锁的性能稍优,但当存在大量读操作时,读写锁能提供更高的效率。在增加协程冲突后,读写锁的性能优势更为显著。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在并发操作中为了防止多任务同时修改共享资源导致的不确定结果,我们可能会用到互斥锁和读写锁。

一:互斥锁
1.互斥锁有两种操作,获取锁和释放锁
2.当有一个goroutine获取了互斥锁后,任何goroutine都不可以获取互斥锁,只能等待这个goroutine将互斥锁释放
3.互斥锁适用于读写操作数量差不多的情况

二:读写锁
1.读写锁有四种操作 读上锁 读解锁 写上锁 写解锁
2.写锁最多有一个,读锁可以有多个(最大个数据说和CPU个数有关)
3.写锁的优先级高于读锁,这是因为为了防止读锁过多,写锁一直堵塞的情况发生
4.当有一个goroutine获得写锁时,其他goroutine不可以获得读锁或者写锁,知道这个写锁释放
5.当有一个goroutine获得读锁时,其他goroutine可以获得读锁,但是不能获得写锁。所以由此也可得知,如果当一个goroutine希望获取写锁时,不断地有其他goroutine在获得读锁和释放读锁会导致这个写锁一直处于堵塞状态,所以让写锁的优先级高于读锁可以避免这种情况,
6.读写锁适用于读多写少的情景。

从上文我们可以得知,互斥锁是非常霸道地,因为一旦有任何一个goroutine获取了互斥锁,其他goroutine都不能获取了,即使这个goroutine可能仅仅只是读取数据而不是修改数据。
而我们想想一个情景,假设现在有三个goroutine:G1,G2,G3都想要读取一段数据A,我们如果用互斥锁的话,就是以下的情形:
G1先加锁,然后读取A,然后释放;然后G2加锁,读取A,释放;G3加锁,读取A,然后释放…这个操作是串行的,由于每个goroutine都需要排队等待前一个goroutine释放锁,所以效率显然不高。
但是如果这个时候我们用读写锁就可以让G1,G2,G3同时读A,就可以大大的提升效率。

三:互斥锁和读写锁性能对比
但是读写锁的效率就一定比互斥锁高吗?这个问题还有待商榷,之前看到一个博主做了实验,认为互斥锁的效率更高,详情:https://www.cnblogs.com/shuiyuejiangnan/p/9457089.html
之后本人把这个博主的代码copy到本地跑了一下发现确实互斥锁不如读写锁优,不过他的代码中的对比有一些问题,在互斥锁的get操作中返回map然后再获取值,在读写锁的get函数中返回的就是int,我将两者都改为获取map的value值发现还是读写锁的性能要好。
原版代码:
在这里插入图片描述

更改后代码:
在这里插入图片描述

不过我想按照自己的思路来进行一下对比。
对比代码如下:

package main

import (
	"fmt"
	"sync"
	"time"
)

const MAXNUM = 1000  //map的大小
const LOCKNUM = 1e7  //加锁次数

var lock sync.Mutex  //互斥锁
var rwlock sync.RWMutex  //读写锁
var lock_map map[int]int  //互斥锁map
var rwlock_map map[int]int  //读写锁map



func main() {
	var lock_w = &sync.WaitGroup{}
	var rwlock_w = &sync.WaitGroup{}
	lock_w.Add(LOCKNUM)
	rwlock_w.Add(LOCKNUM)
	lock_ch := make(chan int, 10000)
	rwlock_ch := make(chan int, 10000)
	lock_map = make(map[int]int, MAXNUM)
	rwlock_map = make(map[int]int, MAXNUM)
	time1 := time.Now()
	for i := 0; i < LOCKNUM; i++ {
		go test1(lock_ch, i, lock_map, lock_w)
	}
	lock_w.Wait()
	time2 := time.Now()
	for i := 0; i < LOCKNUM; i++ {
		go test2(rwlock_ch, i, rwlock_map, rwlock_w)
	}
	rwlock_w.Wait()
	time3 := time.Now()
	fmt.Println("lock time:", time2.Sub(time1).String())
	fmt.Println("rwlock time:", time3.Sub(time2).String())
}

func init_map(a map[int]int, b map[int]int) {  //初始化map
	for i := 0; i < MAXNUM; i++ {
		a[i] = i
		b[i] = i
	}
}

func test1(ch chan int, i int, mymap map[int]int, w *sync.WaitGroup) int {
	lock.Lock()
	defer lock.Unlock()
	w.Done()
	return mymap[i % MAXNUM]
}


func test2(ch chan int, i int, mymap map[int]int, w *sync.WaitGroup) int {
	rwlock.RLock()
	defer rwlock.RUnlock()
	w.Done()
	return mymap[i % MAXNUM]
}

这里列出来加锁次数从1e1到1e7互斥锁和读写锁的耗时对比,由于本人电脑比较渣,做到1e7次加锁就比较慢了,就不想上继续做了,对比表格如下:

加锁次数 互斥锁耗时 读写锁耗时 互斥锁性能好 读写锁性能好
1e1 0s 0s
1e2 996.8µs 0s
1e3 978.7µs 996.7µs
1e4 3.9493ms 2.992ms
1e5 23.9094ms 29.9204ms
1e6 223.3684ms 298.2022ms
1e7 2.3785913s 3.0448529s

其实1e1-1e3次加锁时,互斥锁和读写锁的耗时是很不稳定的,有时互斥锁耗时多,有时读写锁耗时高,在这里我们主要看1e4以上的加锁对比就可以了

到这里我也是很疑惑的,为什么互斥锁的性能竟然比读写锁要好?这不科学啊!!!
在这里插入图片描述
在这里我有一点怀疑:是否golang中sync.Mutex的Lock和Unlock在底层实现的时候要比sync.RW

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值