这里介绍下线程锁的各类。常见的有:互斥锁、自旋锁、读写锁和条件变量。
自旋锁(spinlock)
一条线程加锁锁住临界区,另一条线程尝试访问该临界区的时候,会发生阻塞,但是不会进入休眠状态,并且不断轮询该锁,直至原来锁住临界区的线程解锁。
具体说明:假设一台机器上有两个核心core0
和core1
,现在有线程A、B、C
,此时core0
运行线程A
,core1
运行线程B
,此时线程B
调用spin lock
锁住临界区,当线程A
尝试访问该临界区时,因为B
已经加锁,此时线程A
会阻塞,并且不断轮询该锁,不会交出core0
的使用权,当线程B
释放锁时,A
开始执行临界区逻辑。
skynet中自实现的自旋锁
互斥锁(mutex 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
。
自旋锁和互斥锁的选择
对自旋锁和互斥锁的选择是要根据得到锁的耗时来的,若果当得到锁后,需要执行大量的操作,一般选用互斥锁,若得到锁后,进行很少量的操作,一般选择自旋锁,因为执行的操作短,那么忙等的开销总体还是小于内核态和用户态切换带来的开销的。
条件变量(condition variables)
假设A,B,C
三条线程,其中B,C
线程加了condwait
锁并投入睡眠,而A
线程则在某个条件触发时,会通过signal
通知B,C
线程,从而唤醒B
和C
线程。
互斥锁为什么还要和条件变量配合使用
mutex体现的是一种竞争,我离开了,通知你进来。
cond体现的是一种协作,我准备好了,通知你开始吧。
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起配合使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。
读写锁(readers–writer lock)
某线程加读取锁时,允许其他线程以读模式进入,此时如果有一个线程尝试以写模式访问临界区时,该线程会被阻塞,而其后尝试以读方式访问该临界区的线程也会被阻塞
读写锁适合在读远大于写的情形中使用
skynet中自实现的读写锁
El que tiene tejados de vidrio, no tire piedras a los de su vecino.