很多人搞不清自旋锁和互斥锁的区别,这两种锁都是最底层的锁实现。是很多高级锁的基础。
为什么需要锁?
共享资源是有限的,且同一时间只能有一方占用。就像我们常见的共享单车,它是多人共用的,但是在某个时刻,只能有一个人使用,当我们扫码的时候,它只属于我们,其他人不能占用。这就是加锁的意义。为了保证系统中的共享资源在一个时刻只能有一个线程访问,避免多线程同时使用导致的数据错乱。
锁是谁加的?
任何操作都是由内核完成,线程向内核申请加锁,是否能锁住是由内核决定的。
互斥锁
假设我们目前有两个进程,进程A和进程B。当A需要枷锁的时候会向内核申请获取锁,获取成功后独占互斥锁,进行自己的任务执行。此时B想要获取锁,但此时锁被A占用,所以获取失败,内核将现成B置为休眠状态。等到进程A执行完毕释放锁之后,内核在核实的时机唤醒进程B,成功获取锁后继续执行。
https://blog.csdn.net/qq_37935909/article/details/108625508
需要注意的是在进程B加锁失败后,会从用户态陷入内核态,让内核切换线程,简化了使用锁的难度,但是对性能存在影响。
当两个线程属于同一个进程,因为内存是共享的,所以在切换时虚拟内存这些资源保持不动,只切换线程的私有数据、寄存器等不共享的数据。
上下文的切换耗时比较短,只有几十纳秒到几微秒。如果加锁部分运行的代码比较短,有可能上下文切换的时间比锁住的代码时间还长,就得不偿失了,所以当确定被锁住的代码部分执行时间比较短,就不适合实用互斥锁,应该选择自旋锁。
自旋锁
自旋锁其核心是使用CPU的CAS,在用户态完成加锁和解锁,不会主动产生线程上下文切换,所以比互斥锁来说,时间上会快很多。同时自旋锁不需要线程休眠,对资源的开销也更小。
使用自旋锁的时候,当发生多线程竞争锁的情况,加锁失败的线程会忙等待,直到拿到锁。忙等待可以通过while循环实现,不过最好是使用CPU提供的PAUSE指令来实现。
自旋锁利用CPU周期一直自旋直到锁可用。由于一个自选的线程永远不会放弃CPU,因此在单核CPU上,需要抢占式的调度器(不断通过时钟中断一个线程,运行其他线程)。
自旋的时间和被锁住的代码执行的时间成正比关系。
所以选择互斥锁还是自旋锁,要根据实际情况来看。一些更高级的锁会选择其中一个来实现。