互斥锁
互斥锁(mutex lock : mutual exclusion lock):一条线程加锁锁住临界区,另一条线程尝试访问该临界区的时候,会发生阻塞,并进入休眠状态。临界区是锁 lock 和 unlock 之间的代码片段,一般是多条线程能够共同访问的部分。
具体说明:假设一台机器上的 cpu 有两个核心 core0 和 core1,现在有线程 A、B、C,此时 core0运行线程 A,core1 运行线程 B,此时线程 B 使用 Mutex 锁,锁住一个临界区,当线程 A 试图访问该临界区时,因为线程 B 已经将其锁住,因此线程 A 被挂起,进入休眠状态,此时 core0 进行上下文切换,将线程 A 放入休眠队列中,然后 core0 运行线程 C,当线程 B 完成临界区的流程并执行解锁之后,线程 A 又会被唤醒,core0 重新运行线程 A
自旋锁
自旋锁(spinlock):一条线程加锁锁住临界区,另一条线程尝试访问该临界区的时候,会发生阻塞,但是不会进入休眠状态,并且不断轮询该锁,直至原来锁住临界区的线程解锁。
具体说明:假设一台机器上有两个核心 core0 和 core1,现在有线程 A、B、C,此时 core0 运行线程 A,core1 运行线程 B,此时线程 B 调用 spin lock 锁住临界区,当线程 A 尝试访问该临界区时,因为 B 已经加锁,此时线程 A 会阻塞,并且不断轮询该锁,不会交出 core0 的使用权,当线程 B 释放锁时,A 开始执行临界区逻辑
读写锁
读写锁(readers–writer lock)
概述:读写锁,一共三种状态
- 读状态时加锁,此时为共享锁,当一个线程加了读锁时,其他线程如果也尝试以读模式进入临界区,那么不会发生阻塞,直接访问临界区
- 写状态时加锁,此时为独占锁,当某个线程加了写锁,那么其他线程尝试访问该临界区(不论是读还是写),都会阻塞等待
- 不加锁
注意:
- 某线程加读取锁时,允许其他线程以读模式进入,此时如果有一个线程尝试以写模式访问临界区时,该线程会被阻塞,而其后尝试以读方式访问该临界区的线程也会被阻塞
- 读写锁适合在读远大于写的情形中使用
条件变量
条件变量(condition variables):条件变量是并发编程中的一种同步机制。条件变量使得线程能够阻塞到等待某个条件发生后,再继续执行。假设 A,B,C 三条线程,其中 B,C 线程加了 condwait 锁并投入睡眠,而 A 线程则在某个条件触发时,会通过 signal 通知 B,C 线程,从而唤醒 B 和 C 线程
死锁
当线程 A 持有独占锁 a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。
产生死锁的必要条件:
- 互斥(mutualexclusion),一个资源每次只能被一个进程使用
- 不可抢占(nopreemption),进程已获得的资源,在未使用完之前,不能强行剥夺
- 占有并等待(hold andwait),一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 环形等待(circularwait),若干进程之间形成一种首尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
死锁的解除与预防:
在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。在并发程序中,避免了逻辑中出现复数个线程互相持有对方线程所需要的独占锁的的情况,就可以避免死锁