一、什么是自旋锁

一直以为自旋锁也是用于多线程互斥的一种锁,原来不是!

自旋锁是专为防止多处理器并发(实现保护共享资源)而引入的一种锁机制。自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,“自旋”一词就是因此而得名。自旋锁在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。

自旋锁的初衷就是:在短期间内进行轻量级的锁定。一个被争用的自旋锁使得请求它的线程在等待锁重新可用的期间进行自旋(特别浪费处理器时间),所以自旋锁不应该被持有时间过长。如果需要长时间锁定的话, 最好使用信号量。

自旋锁只有在内核可抢占或SMP(多处理器)的情况下才真正需要,在单CPU且不可抢占的内核下,自旋锁的所有操作都是空操作。

二、自旋锁的缺陷

自旋锁是一种比较低级的保护数据结构或代码片段的原始方式,这种锁可能存在两个问题:

(1)死锁。试图递归地获得自旋锁必然会引起死锁:例如递归程序的持有实例在第二个实例循环,以试图获得相同自旋锁时,就不会释放此自旋锁。所以,在递归程序中使用自旋锁应遵守下列策略:递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。此外如果一个进程已经将资源锁定,那么,即使其它申请这个资源的进程不停地疯狂“自旋”,也无法获得资源,从而进入死循环。

(2)过多占用cpu资源。如果不加限制,由于申请者一直在循环等待,因此自旋锁在锁定的时候,如果不成功,不会睡眠,会持续的尝试,单cpu的时候自旋锁会让其它process动不了。因此,一般自旋锁实现会有一个参数限定最多持续尝试次数。超出后,自旋锁放弃当前time slice,等下一次机会。

由此可见,自旋锁比较适用于锁使用者保持锁时间比较短的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因此只能在进程上下文使用,而自旋锁适合于保持时间非常短的情况,它可以在任何上下文使用。

三、Linux环境下的自旋锁

自旋锁的实现基于共享变量。一个线程通过给共享变量设置一个值来获取锁,其他等待线程查询共享变量是否为0来确定锁现是否可用,然后在忙等待的循环中“自旋”直到锁可用为止。自旋锁的状态值为1表示解锁状态,说明有1个资源可用;0或负值表示加锁状态,0说明可用资源数为0。Linux内核为通用自旋锁提供了API函数初始化、测试和设置自旋锁。API函数功能说明如下表所示:

宏定义

功能说明

spin_lock_init(lock)

初始化自旋锁,将自旋锁设置为1,表示有一个资源可用。

spin_is_locked(lock)

如果自旋锁被置为1(未锁),返回0,否则返回1。

spin_unlock_wait(lock)

等待直到自旋锁解锁(为1),返回0;否则返回1。

spin_trylock(lock)

尝试锁上自旋锁(置0),如果原来锁的值为1,返回1,否则返回0。

spin_lock(lock)

循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。

spin_unlock(lock)

将自旋锁解锁(置为1)。

spin_lock_irqsave(lock, flags)

循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断,将状态寄存器值存入flags。

spin_unlock_irqrestore(lock, flags)

将自旋锁解锁(置为1)。开中断,将状态寄存器值从flags存入状态寄存器。

spin_lock_irq(lock)

循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。关中断。

spin_unlock_irq(lock)

将自旋锁解锁(置为1)。开中断。

spin_unlock_bh(lock)

将自旋锁解锁(置为1)。开启底半部的执行。

spin_lock_bh(lock)

循环等待直到自旋锁解锁(置为1),然后,将自旋锁锁上(置为0)。阻止软中断的底半部的执行。

在实际编程中,何时使用spin_lock,何时使用spin_lock_irq呢?这两者有点区别。

(1)spin_lock

spin_lock 的实现关系为:spin_lock -> raw_spin_lock -> _raw_spin_lock -> __raw_spin_lock ,而__raw_spin_lock 的实现为:

static inline void __raw_spin_lock(raw_spinlock_t *lock)

{

preempt_disable();

spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);

LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);

}

(2)spin_lock_irq

spin_lock_irq 的实现关系为:spin_lock_irq -> raw_spin_lock_irq -> _raw_spin_lock_irq -> __raw_spin_lock_irq,而__raw_spin_lock_irq 的实现为:

static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)

{

local_irq_disable();

preempt_disable();

spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);

LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);

}

由此可见,这两者之间只有一个差别:是否调用local_irq_disable()函数, 即是否禁止本地中断。这两者的区别可以总结为:

在任何情况下使用spin_lock_irq都是安全的。因为它既禁止本地中断,又禁止内核抢占。

spin_lock比spin_lock_irq速度快,但是它并不是任何情况下都是安全的。

举例来说明:进程A中调用了spin_lock(&lock)然后进入临界区,此时来了一个中断(interrupt),该中断也运行在和进程A相同的CPU上,并且在该中断处理程序中恰巧也会spin_lock(&lock)试图获取同一个锁。由于是在同一个CPU上被中断,进程A会被设置为TASK_INTERRUPT状态,中断处理程序无法获得锁,会不停的忙等,由于进程A被设置为中断状态,schedule()进程调度就无法再调度进程A运行,这样就导致了死锁!但是如果该中断处理程序运行在不同的CPU上就不会触发死锁。 因为在不同的CPU上出现中断不会导致进程A的状态被设为TASK_INTERRUPT,只是换出。当中断处理程序忙等被换出后,进程A还是有机会获得CPU,执行并退出临界区。所以在使用spin_lock时要明确知道该锁不会在中断处理程序中使用。

自旋锁和互斥锁实例_多线程编程之自旋锁相关推荐

  1. synchronized不能锁静态变量_多线程编程不可错过——彻底理解synchronized

    持续分享互联网研发技术,欢迎关注我.本人是一线架构师,有问题可以沟通. 1. synchronized简介 在学习知识前,我们先来看一个现象: public class SynchronizedDem ...

  2. java runnable线程锁_多线程 java 同步 、锁 、 synchronized 、 Thread 、 Runnable

    线程 1 线程概述 1.1 什么是线程 v  线程是程序执行的一条路径, 一个进程中可以包含多条线程 v  一个应用程序可以理解成就是一个进程 v  多线程并发执行可以提高程序的效率, 可以同时完成多 ...

  3. educoder 使用线程锁(lock)实现线程同步_性能:Lock的锁之优化

    Lock / synchronized Lock锁的基本操作是通过乐观锁实现的,由于Lock锁也会在阻塞时被挂起,依然属于悲观锁 synchronizedLock实现方式JVM层实现Java底层代码实 ...

  4. 【Java八股文之进阶篇(三)】多线程编程核心之锁框架(一)

    多线程编程核心 在前面,我们了解了多线程的底层运作机制,我们终于知道,原来多线程环境下存在着如此之多的问题.在JDK5之前,我们只能选择synchronized关键字来实现锁,而JDK5之后,由于vo ...

  5. java integer 不变模式_多线程编程的设计模式 不变模式(zt)

    因为字符串类是不变模式最典型的代表,所以其它的知识将在下面继续介绍. 多线程编程的设计模式 不变模式(二) 不变模式(Immutable Pattern)顾名思义,它的状态在它的生命周期内是永恒的(晕 ...

  6. java图形界面多线程_多线程编程、Java I/O系统和Java图形界面编程

    多线程编程: 一个正在运行的程序通常称为一个进程,每一个任务称为一个线程,中能够在一个程序内运行多线程的程序称为多线程程序. 线程与进程的区别:①每个进程都需要操作系统为其分配独立的内存空间: ②而同 ...

  7. 多线程 转账_多线程编程不可错过——彻底理解volatile

    持续分享互联网一线研发技术,欢迎关注我. volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Ja ...

  8. Linux开发_多线程编程

    Linux下的多线程使用的库是pthread,是一个遵守POSIX接口的函数库. Linux下多线程编程对函数定义原型有要求,必须是void *函数名(void *参数名),或者void 函数名(vo ...

  9. java多线程实例_多线程&高并发(全网最新:面试题+导图+笔记)面试手稳心不慌...

    前言 当你开始开始去跳槽面试的时候,明明只是一份15K的工作,却问你会不会多线程,懂不懂高并发,火箭造得让你猝及不防,结果就是凉凉:现如今市场,多线程.高并发编程.分布式.负载均衡.集群等可以说是现在 ...

最新文章

  1. mysql 中的 utf_Mysql中的utf-8竟然是假的!
  2. Windows Phone APP中禁用截图
  3. 点(Dot)与像素(Pixel)的区别
  4. 使用Maven实施自定义JSF 2.0组件
  5. Leetcode算法题(C语言)10--两数之和
  6. Python中各个模块的介绍和使用
  7. HF-NET环境配置与安装
  8. linux基本命令示例_Linux mv命令用法和示例
  9. php自定义类生成lib,thinkphp引入自定义封装类
  10. CP_EndPoint环境中的Hotspot Settings
  11. 计算机网络军训口号,霸气押韵的16字军训口号(精选50句)
  12. 为Linux的ibus添加五笔98输入法
  13. oracle查询锁表进程
  14. SQL Server版本和下载地址
  15. MMA7455加速度传感器測量角度
  16. Dubbo之Adaptive注解用法
  17. 【脑与认知科学期末复习题】
  18. Windows.old可以删除吗?
  19. 夏天吃海鲜的八大禁忌
  20. python输入生日输出星座_python输入日期输出星座?

热门文章

  1. java中匿名类的注意细节
  2. 清明节游戏服务器维护,清明节游戏活动【4月2日--4月16日】
  3. java office文件加水印_文档预览加水印——或可一用的防泄密方式
  4. c语言宏定义比较三个数大小,C语言中两个宏进行大小对比,其中一个没有定义,这种行为如何定义。...
  5. 在vimrc中设置record
  6. Python二级笔记(14)
  7. java char i=2+#039;2#039;;_P039 二维数组的字符按列存放到字符串中 ★★
  8. innobackupex实现导出和导入单张表
  9. Docker原理剖析
  10. 用户权限sudo、suid、sgid以及facl等