《Linux性能优化实战》学习笔记 Day04

06 | 锁:如何根据业务场景选择合适的锁?

原文摘抄

当你无法判断锁住的代码会执行多久时,应该首选互斥锁,互斥锁是一种独占锁。

如果你能确定被锁住的代码执行时间很短,就应该用自旋锁取代互斥锁。

对于 99% 的线程级互斥锁而言,阻塞都是由操作系统内核实现的(比如 Linux 下它通常由内核提供的信号量实现)

自旋锁比互斥锁快得多,因为它通过 CPU 提供的 CAS 函数(全称 Compare And Swap),在用户态代码中完成加锁与解锁操作。在这里插入图片描述

CAS 忙等待

while (true) {
    
    
  //因为判断lock变量的值比CAS操作更快,所以先判断lock再调用CAS效率更高
  if (lock == 0 &&  CAS(lock, 0, pid) == 1) return;
  
  if (CPU_count > 1 ) {
    
     //如果是多核CPU,“忙等待”才有意义
      for (n = 1; n < 2048; n <<= 1) {
    
    //pause的时间,应当越来越长
        for (i = 0; i < n; i++) pause();//CPU专为自旋锁设计了pause指令
        if (lock == 0 && CAS(lock, 0, pid)) return;//pause后再尝试获取锁
      }
  }
  sched_yield();//单核CPU,或者长时间不能获取到锁,应主动休眠,让出CPU
}

这是两种最基本的处理方式,更高级别的锁都会选择其中一种来实现,比如读写锁就既可以基于互斥锁实现,也可以基于自旋锁实现。

如果你能够明确区分出读和写两种场景,可以选择读写锁。

因此,读写锁真正发挥优势的场景,必然是读多写少的场景,否则读锁将很难并发持有。

用队列把请求锁的线程排队,按照先来后到的顺序加锁即可,当然读线程仍然可以并发,只不过不能插队到写线程之前。

乐观锁全程并没有加锁,所以它也叫无锁编程。

乐观锁虽然去除了锁操作,但是一旦发生冲突,重试的成本非常高。所以,只有在冲突概率非常低,且加锁成本较高时,才考虑使用乐观锁。

读写锁是有倾向性的,读优先锁很高效,但容易让写线程饿死,而写优先锁会优先服务写线程,但对读线程亲和性差一些。

协程自旋锁

Go里的自旋锁需要自己实现,方便协程调度。协程使用自旋锁的时候,这是spinLock 的Lock方法


for !atomic.CompareAndSwapUint32(sl, 0, 1) {
    
    
    runtime.Gosched()
}

其中runtime.Gosched,是把阻塞的协程调度出去,这样调度器可以执行其他协程。

心得体会

锁用操作系统封装好的接口调用,会好方便。但是很多场景都是要封装高级锁的。

工作体验

CAS相对用的较少,没啥特别高的并发量,锁还是方便的。23333。

可能在分布式集群里,分布式锁比单机的锁用的要多一点。

就怕锁都不知道要锁的。

猜你喜欢

转载自blog.csdn.net/a17816876003/article/details/128744463