为什么需要锁
我们知道线程是可以共享进程的资源的,当多个进程同时使用同一个资源的时候,就会发生问题,下图展示了两个线程对同一个变量进行加法运算时的情况,理想状态下。A线程对i=50进行加+1后i=51,之后cpu切换线程B继续进行加1运算,此时i=52。但程序在实际运行中可能会出现错误,如下图:
当多线程相互竞争操作共享变量时,由于运气不好,即在执行过程中发生了上下文切换,我们得到了错误的结果,称之为竞争。操作共享资源的发代码成为临界区,为了解决竞争问题,我们可以让临界区互斥,也就是一个线程在临界区时,另一个线程应该阻塞。
互斥的实现
为了实现线程的互斥,我们引入了两种方法
- 锁:加锁、解锁操作;
- 信号量:P、V 操作;
互斥锁与自旋锁
若线程先进入临界区,需要先进行加锁操作,如果加锁顺利则可以进入临界区,如果临界区执行完后,再进行解锁,释放临界区的资源
自旋锁:当前的线程无法获取锁时候回一直循环,不做任何事情,称为忙等待锁。如果是单CPU的话,不会运行其他线程,所以自旋锁永远也不会得到临界资源,从而一直等待,CPU也不会切换执行其他线程,所以自旋锁会一直占用CPU
无等待锁:也称互斥锁,无等待锁是相对于自旋锁说的,当线程拿不到资源的时候,不会占用CPU资源等待,而是将线程放入到锁的等待队列,然后将CPU让给其他线程。当线程 A 加锁成功后,此时互斥锁已经被线程 A 独占了,只要线程 A 没有释放手中的锁,线程 B 加锁就会失败,于是就会释放 CPU 让给其他线程,既然线程 B 释放掉了 CPU,自然线程 B 加锁的代码就会被阻塞。对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的。
正是由于内核帮我们切换进程,所以互斥锁存在着性能开销,来自于线程的两次切换:
- 线程加锁失败时,内核会把线程的状态从「运行」状态设置为「睡眠」状态,然后把 CPU 切换给其他线程运行;
- 接着,当锁被释放时,之前「睡眠」状态的线程会变为「就绪」状态,然后内核会在合适的时间,把 CPU 切换给该线程运行。
所以,如果你能确定被锁住