如何高效的使用锁

我们很多时候在使用共享数据的时候,往往会使用锁来保护,最常用的锁就是互斥锁了。如之前的博文介绍的,锁一般分为:互斥锁、自旋锁、读写锁、乐观锁等。锁有这么多种,那么我们应该怎么选择锁,以提供更好的性能呢?首先我们得知道加锁的成本,其次是访问共享资源冲突的概率,再次是共享资源的特性如读多写少。

自旋锁与互斥锁,休眠还是忙等待

锁的实现是有层级的,锁基本都是基于自旋锁或者互斥锁实现的。
互斥锁加锁成本更高,当发生并发冲突时,获取不到锁的线程将会堵塞,而堵塞线程往往需要内核的参与,从而导致线程获取锁失败时,增加了两次上下文切换的成本:从运行中切换为休眠,以及锁释放时从休眠状态切换为运行中。上下文切换耗时在几十纳秒到几微秒之间,或许这段时间比锁住的代码段执行时间还长。
自旋锁则相反,当获取锁失败时,线程不会发生上下文切换,而是进入忙等待。自旋锁的加锁是通过CPU提供的CAS指令,在用户态中完成。加锁流程包括 2 个步骤:第 1 步查看锁的状态,如果锁是空闲的,第 2 步将锁设置为当前线程持有,故 CAS(lock, 0, pid)如果成功,就表示自旋锁的加锁操作成功。
如果加锁的时间比较短并且并发冲突不是很严重,那么自旋锁等锁与执行锁资源的代码一般不会发生上下文切换,否则CPU将长时间被忙等待占有,从而导致并发下降,此时我们应该使用互斥锁。但是当你无法判断锁住的代码会执行多久时,应该首选互斥锁。

读写锁

读写锁由读锁与写锁组成,当仅读取共享资源时,使用读锁;当需要修改共享资源时,使用写锁。写锁是一种独占锁,当获取到写锁时,其他线程都不能获取到读锁与写锁。读锁则是一种共享锁,不同线程间可以同时获取读锁。如果共享资源是读多写少,则可以用读写锁。
读写锁又可以分为读优先读写锁、写优先读写锁与公平读写锁。
读优先读写锁更强调效率,它期待锁能被更多的线程持有。当线程A先持有读锁后,即使线程B在等待写锁,后续前来获取读锁的线程C仍然可以立刻加锁成功,这样就有 A、C这2个读线程在并发持有锁,效率更高。但如果读线程源源不断的来,那么会导致写线程饿死。
写优先读写锁则相反,线程C获取读锁会失败,它将被阻塞在获取锁的代码中,这样,只要线程A释放读锁后,线程B马上就可以获取到写锁。写优先锁可以保证写线程不会饿死,但如果新的写线程源源不断地到来,读线程也可能被饿死。
公平读写锁通过用队列把请求锁的线程排队,按照先来后到的顺序加锁即可,当然读线程仍然可以并发,只不过不能插队到写线程之前。

乐观锁

乐观锁,就是假定冲突的概率很低,所以它采用的“加锁”方式是,先修改完共享资源,再验证这段时间内有没有发生冲突。如果没有其他线程在修改资源,那么操作完成。如果发现其他线程已经修改了这个资源,就放弃本次操作,然后进行重试。那么怎么判断操作期间是否有修改呢?这个判断标准需要避免ABA问题,比如对于变量a,线程1CAS开始时的初始值为a=1,在线程1修改a期间,线程2a改成2后在改成1,那么线程1检验a时还是与初始值一样,但是变量a其实已经修改过多次了。这就是ABA问题,我们一般通过版本号来解决ABA问题,每次修改时都自增版本号。在多线程编程中乐观锁是基于CAS实现的,也称为无锁编程,广泛应用于消息队列中。其实在分布式领域,也广泛应用于数据库,云存储中。

基于CAS实现的stamped_lock-比读写锁更高效的锁

在读写锁的读多写少的场景中,既然是读多写少,那么我们能不能同时利用乐观锁与读写锁呢?其实是可以的,这就是stamped_lock。stamped_lock有三种锁:
1、写锁,是个排它锁或者叫独占锁,同时只有一个线程可以获取该锁,当一个线程获取该锁后,其它请求的线程必须等待,当目前没有线程持有悲观读锁或者写锁的时候才可以获取到该锁;
2、悲观读锁,是个共享锁,在没有线程获取独占写锁的情况下,同时多个线程可以获取该锁,如果已经有线程持有写锁,其他线程请求获取该读锁会被阻塞;
3、乐观读锁,是相对于悲观锁来说的,在操作数据前并没有通过CAS设置锁的状态,如果当前没有线程持有写锁,则简单的返回一个非0的stamp版本信息,获取该stamp后在具体操作数据前还需要调用验证下该stamp是否已经被修改。
写锁与悲观读锁类似于读写锁,可以使用互斥锁实现,也可以使用自旋锁实现。
那么怎么使用stamped_lock:
1、可以先使用乐观读锁获取一个stamp并且判断stamp是否合法,然后将共享数据拷贝到线程的使用内存中(如栈变量),然后校验stamp是否改变,如果改变说明有其他线程修改了共享变量或者正在修改共享变量,那么就用悲观读锁获取读锁,否则使用线程私有变量保存的值计算结果。这个是stamp的经典用法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值