自旋锁 Spin lock 的实现是为了保护一段短小的临界区操作代码,保证这个临界区的操作是原子的,从而避免并发的竞争冒险。在Linux内核中,自旋锁通常用于包含内核数据结构的操作,你可以看到在许多内核数据结构中都嵌入有spinlock,这些大部分就是用于保证它自身被操作的原子性,在操作这样的结构体时都经历这样的过程:上锁->操作->解锁。
如果内核控制路径发现自旋锁“开着”(可以获取),就获取锁并继续自己的执行。相反,如果内核控制路径发现锁由运行在另一个CPU上的内核控制路径“锁着”,就在原地“旋转”,反复执行一条紧凑的循环检测指令,直到锁被释放。 自旋锁是循环检测“忙等”,即等待时内核无事可做(除了浪费时间),进程在CPU上保持运行,所以它保护的临界区必须小,且操作过程必须短。不过,自旋锁通常非常方便,因为很多内核资源只锁1毫秒的时间片段,所以等待自旋锁的释放不会消耗太多CPU的时间。
自旋锁需要阻止在代码运行过程中出现的任何并发干扰。这些“干扰”包括: 中断,包括硬件中断和软件中断 (仅在中断代码可能访问临界区时需要)、内核抢占(仅存在于可抢占内核中)、其他处理器对同一临界区的访问 (仅SMP系统)。spin lock需要在芯片底层实现物理上的内存地址独占访问,并且在实现上使用特殊的汇编指令访问
以arm为例,从存在SMP的ARM构架指令集开始(V6、V7),采用LDREX和STREX指令实现真正的自旋等待。
https://blog.csdn.net/electrombile/article/details/51289813 linux_spinlock_实现机制
http://blog.chinaunix.net/uid-20543672-id-3252604.html 深入分析Linux自旋锁
https://www.cnblogs.com/alantu2018/p/8447497.html Linux内核同步 - spin_lock
--------------------------------------------------------------------------------------------------------------
互斥锁、读写锁和自旋锁的区别:
从 实现原理上来讲,Mutex属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待。 一次只能一个线程拥有互斥锁,其他线程只有等待。
Spin lock则不然,它属于busy-waiting类型的锁,如果线程A是使用pthread_spin_lock操作去请求锁,那么线程A就会一直在 Core0上进行忙等待并不停的进行锁请求,直到得到这个锁为止。 自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是 否该自旋锁的保持者已经释放了锁。
自旋锁的效率远 高于互斥锁。虽然它的效率比互斥锁高,但是它也有些不足之处:
1、自旋锁一直占用CPU,在未获得锁的情况下,一直运行-----自旋,所以占用着CPU,如果不能在很短的时 间内获得锁,这无疑会使CPU效率降低。
2、在用自旋锁时有可能造成死锁,当递归调用时有可能造成死锁,调用有些其他函数也可能造成死锁,如 copy_to_user()、copy_from_user()、kmalloc()等。
TIP:
-
自旋锁只有在内核可抢占式或SMP的情况下才真正需要,在单CPU且不可抢占式的内核下,自旋锁的操作为空操作。自旋锁适用于锁使用者保持锁时间比较短的情况下。
读写锁有三种状态:读加锁状态、写加锁状态和不加锁状态。
只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁,这也是它可以实现高并发的原因。
-
当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;
-
如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放;
-
为了避免想要尝试写操作的线程一直得不到写状态锁,当处于读模式的读写锁接收到一个试图对其进行写模式加锁操作时,便会阻塞后面对其进行读模式加锁操作的线程。 即当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。
【如果预感有写的期望,那么后续的读加锁将不被受理。】
1)多个读者可以同时进行读
2)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)
RCU锁(Read-Copy Update)
RCU中,读者不需要使用锁,要访问资源尽管访问就好了。
RCU中,写者的同步开销比较大,要等到所有的读者都访问完成了才能够对被保护的资源进行更新。
写者修改数据前首先拷贝一个被修改元素的副本,然后在副本上进行修改,修改完毕后它向垃圾回收器注册一个回调函数以便在适当的时机执行真正的修改操作。读者必须提供一个信号给写者以便写者能够确定数据可以被安全地释放或修改的时机。有一个专门的垃圾收集器来探测读者的信号,一旦所有的读者都已经发送信号告知它们都不在使用被RCU保护的数据结构,垃圾收集器就调用回调函数完成最后的数据释放或修改操作。