专栏首页物流IT圈多线程编程之自旋锁

多线程编程之自旋锁

一、什么是自旋锁

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

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

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

  自旋锁只有在内核可抢占或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时要明确知道该锁不会在中断处理程序中使用。

本文分享自微信公众号 - 物流IT圈(exiter18)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-12-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 物联网知识科普(4)——室内实时定位技术及原理

    RTLS即 Real Time Location Systems的简称,实时定位系统。

    物流IT圈
  • 微服务系统之认证管理详解

    微服务大行其道,微服务安全也是非常热门的话题。本文向大家分享微服务系统中认证管理相关技术。其中包括用户认证、网关和 API 认证、系统间和系统内的认证。

    物流IT圈
  • 传统企业微服务落地大法(4)-真正的微服务化阶段

    (坦白来说,传统企业到这个程度难于登天,不仅仅需要完全独立的IT公司,比较独立的IT文化,还要能走向市场,变成真正的利润中心)

    物流IT圈
  • 转:自旋锁(spinlock)

    自旋锁与互斥锁有点类似,只是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就...

    流柯
  • openshift/origin学习记录(3)——添加Registry

    学习资料来源于官方英文文档与《开源容器云OpenShift》一书,因为刚开始学习,不确定博客的正确性,以下内容仅供参考。 本部分是在openshift/orig...

    胡了了
  • 观点 | 王煜全:未来哪三种人不会被人工智能取代

    未来都是加速型增长的,未来都是不可预期的。所以在这个不可预期的世界,大家将听到一个好消息和一个坏消息。

    华章科技
  • 第一张黑洞照片全靠VLBI,这个Github项目教你用Python实现

    【新智元导读】哈佛学生写的Python模块,用于模拟和操作VLBI数据并使用正则化最大似然法生成图像,模拟黑洞成像的算法。进入Github飙升榜TOP 3,超过...

    诸葛青云
  • 微信小程序Ⅷ [WXSS 小知识积累]

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011415782/article/de...

    泥豆芽儿 MT
  • 1000+倍!超强Python『向量化』数据处理提速攻略

    1000倍的速度听起来很夸张。Python并不以速度著称。这是真的吗?当然有可能 ,关键在于你如何操作!

    量化投资与机器学习微信公众号
  • 微信小程序的好处及可能的不足

    微信小程序是什么?小程序基于微信体系,在微信内部不用安装就能使用,体积不超过1 M。如果简单粗暴一点,小程序可以简单理解为——“微信应用”。 引用微信之父张小龙...

    ytkah

扫码关注云+社区

领取腾讯云代金券