AUTOSAR多核OS为实现核间资源互斥,保证数据一致性,设计了自旋锁机制,该机制适用于核间资源互斥。对于多核概念,需要一种新的机制来支持不同内核上任务的互斥。这种新机制不应在同一内核上的 TASK 之间使用,因为它没有意义。在这种情况下,AUTOSAR 操作系统将返回错误。

自旋锁的特点就是当一个线程获取了锁之后,其他试图获取这个锁的线程一直在循环等待获取这个锁,直至锁重新可用。由于线程实在一直循环的获取这个锁,所以会造成CPU处理时间的浪费,因此最好将自旋锁用于能很快处理完的临界区。

一旦一个锁变量被 TASK/ISR2 占用,其他内核上的其他 TASK/ISR2 将无法占用该锁变量。自旋锁机制不会在轮询锁定变量时取消调度这些其他 TASK。但是,在轮询锁定变量时,可能会发生具有更高优先级的 TASK/ISR 准备就绪的情况。在这种情况下,旋转任务将受到干扰。

1. 干扰引起的死锁情况,在同一个核Core0内,高优先级 TASK 无限旋转(循环的获取这个锁),因为低优先级 TASK 已经占用了自旋锁。在这种情况下,第二个 GetSpinlock 调用将返回错误。【当高优先级向低优先级的task一直申请自旋锁的情况】

2. 用户可以保护 TASK 免受【当高优先级向低优先级的task一直申请自旋锁的时,因为低优先级 TASK 已经占用了自旋锁,第二个 GetSpinlock 调用将返回错误】的影响,例如,使用 SuspendAllInterrupts 敲击旋转锁,这样它就不会受到其他 TASKS 的干扰。操作系统可以为调用方自动执行此操作,请参阅配置参数 OsSpinlockLockMethod。

下图显示了由两个不同内核上的 TASKS 以不同顺序获取的两个旋转锁导致的典型死锁

3. 为避免死锁,不允许嵌套不同的旋转锁。如果旋转锁必须要嵌套,则必须定义唯一的顺序。旋转锁只能按此顺序进行,在定义的顺序内不允许循环,允许跳过单个旋转锁。

此图显示了一个示例,其中两个 TASKS 可以访问一组旋转锁 S1 -- S6。允许按预定义的顺序占用旋转锁,并允许跳过旋转锁。如果同时占用多个旋转锁,则必须按严格的后进先出顺序进行锁定和解锁。

4. 小结

AUTOSAR OS为了避免同一个内核上自旋锁造成的死锁,在任何任务或者中断占用自旋锁时,OS会自动挂起所有的中断,不会被同一内核上的任务或者中断抢占。但是,如果核间任务嵌套请求占用自旋锁,就有可能导致任务的相互锁死。比如,核0上的某任务请求先占用自旋锁A,再占用自旋锁B;而核1上的另一个任务请求先占用自旋锁B,再占用自旋锁A。如果这两个任务开始运行的时间间隔很近,就有可能造成任务的互相锁死,即自旋锁的回环嵌套调用导致的死锁。所以在系统设计时,禁止回环嵌套使用自旋锁。如果实在需要嵌套使用自旋锁,那么需要严格按照顺序请求。比如核0上的某任务请求先占用自旋锁A,再占用自旋锁B;核1上的另一个任务也先请求占用自旋锁A,再占用自旋锁B,这样顺序地访问自旋锁就不会造成死锁。

自旋锁使用时有2点需要注意:

  1. 自旋锁是不可递归的,递归的请求同一个自旋锁会自己锁死自己。(递归)
  2. 线程获取自旋锁之前,要禁止当前处理器上的中断。(防止获取锁的线程和中断形成竞争条件) 
      比如:当前线程获取自旋锁后,在临界区中被中断处理程序打断,中断处理程序正好也要获取这个锁, 
      于是中断处理程序会等待当前线程释放锁,而当前线程也在等待中断执行完后再执行临界区和释放锁的代码。

中断处理下半部的操作中使用自旋锁尤其需要小心:

  1. 下半部处理和进程上下文共享数据时,由于下半部的处理可以抢占进程上下文的代码, 
      所以进程上下文在对共享数据加锁前要禁止下半部的执行,解锁时再允许下半部的执行。
  2. 中断处理程序(上半部)和下半部处理共享数据时,由于中断处理(上半部)可以抢占下半部的执行, 
    所以下半部在对共享数据加锁前要禁止中断处理(上半部),解锁时再允许中断的执行。
  3. 同一种tasklet不能同时运行,所以同类tasklet中的共享数据不需要保护。
  4. 不同类tasklet中共享数据时,其中一个tasklet获得锁后,不用禁止其他tasklet的执行,因为同一个处理器上不会有tasklet相互抢占的情况
  5. 同类型或者非同类型的软中断在共享数据时,也不用禁止下半部,因为同一个处理器上不会有软中断互相抢占的情况

自旋锁(spinlock)与互斥锁的区别:

加锁的目的就是保证共享资源在任意时间里,只有一个线程访问,这样就可以避免多线程导致共享数据错乱的问题。

当已经有一个线程加锁后,其他线程加锁则就会失败,互斥锁和自旋锁对于加锁失败后的处理方式是不一样的:

  • 互斥锁加锁失败后,线程会释放 CPU ,给其他线程;
  • 自旋锁加锁失败后,线程会忙等待,直到它拿到锁;

互斥锁是一种「独占锁」,比如当线程 A 加锁成功后,此时互斥锁已经被线程 A 独占了,只要线程 A 没有释放手中的锁,线程 B 加锁就会失败,于是就会释放 CPU 让给其他线程,既然线程 B 释放掉了 CPU,自然线程 B 加锁的代码就会被阻塞。

对于互斥锁加锁失败而阻塞的现象,是由操作系统内核实现的。当加锁失败时,内核会将线程置为「睡眠」状态,等到锁被释放后,内核会在合适的时机唤醒线程,当这个线程成功获取到锁后,于是就可以继续执行。所以,互斥锁加锁失败时,会从用户态陷入到内核态,让内核帮我们切换线程,虽然简化了使用锁的难度,但是存在一定的性能开销成本。

那这个开销成本是什么呢?会有两次线程上下文切换的成本:

  • 当线程加锁失败时,内核会把线程的状态从「运行」状态设置为「睡眠」状态,然后把 CPU 切换给其他线程运行;
  • 接着,当锁被释放时,之前「睡眠」状态的线程会变为「就绪」状态,然后内核会在合适的时间,把 CPU 切换给该线程运行。

线程的上下文切换的是什么?

当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据

上下切换的耗时有大佬统计过,大概在几十纳秒到几微秒之间,如果你锁住的代码执行时间比较短,那可能上下文切换的时间都比你锁住的代码执行时间还要长。

所以,如果你能确定被锁住的代码执行时间很短,就不应该用互斥锁,而应该选用自旋锁,否则使用互斥锁

自旋锁是通过 CPU 提供的 CAS 函数(Compare And Swap),在「用户态」完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁来说,会快一些,开销也小一些。

一般加锁的过程,包含两个步骤:

  • 第一步,查看锁的状态,如果锁是空闲的,则执行第二步;
  • 第二步,将锁设置为当前线程持有;

CAS 函数就把这两个步骤合并成一条硬件级指令,形成原子指令,这样就保证了这两个步骤是不可分割的,要么一次性执行完两个步骤,要么两个步骤都不执行。

使用自旋锁的时候,当发生多线程竞争锁的情况,加锁失败的线程会「忙等待」,直到它拿到锁。这里的「忙等待」可以用 while 循环等待实现,不过最好是使用 CPU 提供的 PAUSE 指令来实现「忙等待」,因为可以减少循环等待时的耗电量。

自旋锁是最比较简单的一种锁,一直自旋,利用 CPU 周期,直到锁可用。需要注意,在单核 CPU 上,需要抢占式的调度器(即不断通过时钟中断一个线程,运行其他线程)。否则,自旋锁在单 CPU 上无法使用,因为一个自旋的线程永远不会放弃 CPU。

自旋锁开销少,在多核系统下一般不会主动产生线程切换,适合异步、协程等在用户态切换请求的编程方式,但如果被锁住的代码执行时间过长,自旋的线程会长时间占用 CPU 资源,所以自旋的时间和被锁住的代码执行的时间是成「正比」的关系,我们需要清楚的知道这一点。

自旋锁与互斥锁使用层面比较相似,但实现层面上完全不同:当加锁失败时,互斥锁用「线程切换」来应对,自旋锁则用「忙等待」来应对

AUTOSAR-自旋锁(spinlock)与互斥锁相关推荐

  1. C#线程锁(自旋锁SpinLock、互斥锁Mutex、混合锁Monitor | lock)

    一.自旋锁 自旋锁是指当一个线程在获取锁对象的时候,如果锁已经被其它线程获取,那么这个线程将会循环等待,不断的去获取锁,直到获取到了锁.适合于原子操作时间非常短的场景 优点:避免了线程上下文切换.性能 ...

  2. 互斥锁机制,互斥锁与读写锁区别

    Linux的4种锁机制: 互斥锁:mutex,用于保证在任何时刻,都只能有一个线程访问该对象.当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒 读写锁:rwlock,分为读锁和写锁.处于读操作时 ...

  3. 同步方法中的锁对象_互斥锁与读写锁:如何使用锁完成Go程同步?

    图转自https://colobu.com/2018/12/18/dive-into-sync-mutex/ 这张图容易让人产生误解,容易让人误以为goroutine1获取的锁,只有goroutine ...

  4. python互斥锁原理_Linux 互斥锁的实现原理(pthread_mutex_t)

    引言 互斥锁大都会使用,但是要了解其原理就要花费一番功夫了.尽管我们说互斥锁是用来保护一个临界区,实际上保护的是临界区中被操纵的数据. 互斥锁还是分为三类:快速互斥锁/递归互斥锁/检测互斥锁 fute ...

  5. android 线程互斥锁,多线程编程-互斥锁

    1.引言: 互斥锁,是一种信号量,常用来防止两个进程或线程在同一时刻访问相同的共享资源.可以保证以下三点: 原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果 ...

  6. python多线程锁_Python多线程互斥锁使用

    # 代码 # coding=utf-8 """通过使用互斥锁,锁定全局变量,防止数据异常""" import threading num = ...

  7. python的锁机制_python互斥锁、加锁、同步机制、异步通信知识总结

    某个线程要共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能更改:直到该线程释放资源,将资源的状态变成"非锁定",其他的线程才能再次锁定该资源.互斥 ...

  8. 8-26-GLI锁与普通互斥锁、死锁问题、递归锁、信号量、Event事件、并发的tcp通信、进程池线程池

    昨日回顾1 生产者消费者-在生产者和消费者之间,通过队列,增加缓冲,避免了生产者和消费者之间交互-Queue,redis,rabbitmq,kafka-解耦合,队列是微服务的基础2 线程理论,开启-进 ...

  9. 高效编程之互斥锁和自旋锁

    两种锁的加锁原理 互斥锁:线程会从sleep(加锁)-->running(解锁),过程中有上下文的切换,cpu的抢占,信号的发送等开销. 自旋锁:线程一直是running(加锁-->解锁) ...

  10. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等...

    http://blog.51cto.com/13919357/2339446 Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容 ...

最新文章

  1. php实现人员权限管理(用户界面)
  2. SQL SERVER 2005 CTE(通用表达式)
  3. python 画pr曲线
  4. PCL中异常处理机制
  5. Web开发中8个基础常见功能
  6. java 内存 开发 经验_有一到五年开发经验的JAVA程序员需要掌握的知识与技能!...
  7. python安装详细步骤mac_mac如何安装python3
  8. RestEasy传值方式
  9. struts2拦截器
  10. 什么是 Token 令牌
  11. csharp进阶练习题:谜机 - 第1部分:插板【难度:2级】--景越C#经典编程题库,不同难度C#练习题,适合自学C#的新手进阶训练
  12. 驾驶证体检医院(114---2 挂对应的医院司机体检)
  13. sd卡怎么格式化?5个步骤轻松教会你
  14. R语言——矩阵中删除缺省值可用的函数
  15. Kruskal重构树 学习笔记
  16. 学习笔记(15):C++编程FFMpeg(QT5+OpenCV)实战--实时美颜直播推流-opencv播放rtsp海康摄像头和播放系统摄像头...
  17. “食族人”商标不具有不良影响,二审被驳回上诉!
  18. 2022-2028全球工业用视频内窥镜行业调研及趋势分析报告
  19. pytorch深度学习框架--gpu和cpu的选择
  20. 打开新世界的大门。。吧?

热门文章

  1. 天津高一学业水平测试计算机,2019年1月天津高中学业水平考试标准
  2. 2022年PMP考试中敏捷占多少?
  3. 万兴pdf编辑解压后打不开_如何使用万兴PDF专家编辑PDF文档?
  4. Ubuntu构筑LiteIDE的Go语言开发环境
  5. rda-摘自metalink[ID 414966.1]
  6. [附源码]计算机毕业设计Python高校体育场馆管理系统(程序+源码+LW文档)
  7. 记一次与第三方ROM上的360软件全家桶的斗智斗勇难忘的经历
  8. 【STM32】贪吃蛇小游戏
  9. linux 下加载cp2102串口驱动
  10. cURL无法访问TLS网站故障解决