首先看一下问题引出,先看一些经典的问题.

多线程的隐患

首先我们利用多线程的话肯定是好处多多,因为我们可以同时去做一些事情,大大的提高了效率.像我们下载视频的时候就可以同时下载多个视频,这样是节省了很多时间,用户体验也会更好.但是用得时候也会存在一些安全隐患,比如同一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,这样会出现一些数据错乱和数据安全的问题.下面我们就看一些例子.

存钱取钱案例

比如我现在有1000元,同时有2个线程去处理,一个线程是取钱100元,一个线程是存钱100元,我制作了一张示意图如下:

存钱取钱示意图

我们从上面的图应该很清楚,存100、取100的最终结果就是还是剩余1000元,但是看我们上面的示意图,最终结果要么是900要么是1100,这就与1000的结果对不上,所以很明显用多线程是会存在隐患,下面我们用代码演示一下上面的结果:

卖票案例

这个和上面的稍微有点差别,因为上面的是2个操作,而卖票呢,它是1个操作.同样的比如我现在有1000张票,同时有2个线程去处理卖票,一个线程是一个线程卖票100张,另一个线程也是卖票100张,同时操作的话,也会出现异常,我制作了一张示意图如下:

我们同样的也可以用代码演示一下效果:

上面的两个,大家可以试试.接下来针对上面的问题,我们就引出了今天的主角,线程同步技术

线程同步技术

解决方案:使用线程同步技术 (同步,就是协同步调,按照预定的先后次序进行运行),我们先来看下线程同步技术有哪些方案:线程同步方案最常见的技术就是:加锁.大概方案如下(大致这么多方案,当然还有其他的)

OSSpinLock (自旋锁)

os_unfair_lock (互斥锁)

pthread_mutex (互斥锁、递归锁)(里面3种类型,目前只说2种对我们有用的)

dispatch_queue (DISPATCH_QUEUE_SERIAL)

NSLock

NSRecursiveLock

NSCondition

NSConditionLock

@synchronized

以上的这些都是可以做到线程同步方案,我会一个一个介绍,并且介绍它们的优缺点,性能怎么样,我们怎么去选择等等.我们就以上面的例子讲解.

OSSpinLock (自旋锁)

OSSpinLock 叫做 "自旋锁", 等待锁的状态会处于忙等( busy-wait )状态,一直占用着CPU内存.头文件导入<libkern/OSAtomic.h>,而且这个锁是过期锁,iOS10以后就过期了,但是我们还是来看看,因为面试中可能会遇到,用法如下:

初始化锁:OSSpinLock  spinLock = OS_SPINLOCK_INIT;

加锁: OSSpinLockLock(&_spinLock);

解锁: OSSpinLockUnlock(&_spinLock);

我们先看卖票:

我是加锁和解锁了,为什么上面的代码还有问题,有发现原因的吗?这是因为SpinLock是局部变量,所以我们进去都是初始化了一把新锁,这把锁并没有被使用过,是达不到加锁的目的.所以所有的线程都是用同一把锁才能达到加锁的目的.请看下面代码:

确实是剩下85张,没有问题.原理是这样,每次执行saleTicket都会进入  //加锁: OSSpinLockLock(&_SpinLock) 这个代码,第一次进来是正常给_SpinLock加锁,第二次进来的时候,发现_SpinLock已经被人加了锁,它会在这边等待,等待这把锁被解锁,解锁完了以后然后它再去加锁,就这样依次进行,就保证了里面的那段代码同时只有一个线程在处理.这就解决了线程同步的问题.

接下来我们就看存钱和取钱的问题.存钱、取钱是2个操作,我们是用同一把锁还是2把锁?思考了这个问题我们就知道怎么做了,因为存钱和取钱是不能同时进行,所以我们就用同一把锁即可(2把锁是有问题的大家可以自己试试),请看下面的代码,我们验证一下:

自旋锁"忙等"状态是怎么等呢?忙等就是一直忙碌,而且还在等待,类似这样while(锁还没有解开),就会一直执行,占用cpu,直到锁被放开.而且OSSpinLock是已经过期了,而且目前已经不再安全,可能会出现优先级反转的问题.

下面说一下线程的调度问题

其实你看上面的图,如果随着时间的推移,操作系统把时间给thread1一点时间,再给thread2一点时间,再给thread3一点时间,而且这个时间周期非常短,就这样一直非常快的切换,这样下来给我们的感觉就是同时执行.这就是实现多线程的一个方案.也就是多线程的原理,我们也可以说这是时间片轮转调度算法.调用进程或者线程都是用这套算法

还有个就是线程的优先级问题,比如thread1的优先级比较高,那么操作系统就会给thread1多一点时间去执行.其他的线程就少一点时间去执行.这样的话,我们使用自旋锁就会存在一个优先级反转的问题.比如thread1优先级非常高,thread2优先级很低.首先是thread2先进去加锁,thread1再进来就会等thread2解锁,由于thread1的优先级非常高,CPU就把大量的时间给了thread1,此时可能导致thread2没有时间执行解锁,thread1就会一直执行等待,有点死锁的感觉.

这样大家想一想:如果优先级高的不是忙等,而是休眠,休息就不会占用CPU,那不就是解决了这个问题.

os_unfair_lock (互斥锁)

os_unfair_lock用于取代不安全OSSpinLock,是从iOS10开始支持.

从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等(后面会证明一下)

它的用法和OSSpinLock非常像,需要倒入头文件<os/lock.h>,用法如下:

初始化锁:os_unfair_lock  unfairLock = OS_UNFAIR_LOCK_INIT;

加锁: os_unfair_lock_lock(&_unfairLock);

解锁:os_unfair_lock_unlock(&_unfairLock);

下面我们就去看一下用法

存钱和取钱也是一样的道理,我们可以自己试试.

pthread_mutex (互斥锁)

像这种pthread开头的一般都是跨平台的Windows、linux等等都是可以用的,mutex叫做"互斥锁",等待锁的线程会处于休眠状态

其实用法都是差不多,我们先来看下怎么用,这个稍微代码多一点点

 //初始化属性

    pthread_mutexattr_t attr;

    pthread_mutexattr_init(&attr);

    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);

    //初始化锁

    pthread_mutex_init(&_mutex, &attr);(&attr也可以传NULL,这样的话,上面的都是默认的,上面可以都不用写)

    //加锁

    pthread_mutex_lock(&_mutex);

    //解锁

    pthread_mutex_unlock(&_mutex);

    //销毁相关资源

    pthread_mutexattr_destroy(&attr);

    pthread_mutex_destroy(&_mutex);

其中PTHREAD_MUTEX_NORMAL是锁的类型,后面会细说,先传默认PTHREAD_MUTEX_NORMAL

先看运行结果:

没有问题,记得销毁哈,之前说的2个锁,没有提供销毁的方法,那我们就不写,如果提供了,我们还是写一下的好!

pthread_mutex (递归锁)

我们再看另一种情况,请看下面的代码:

上面这些代码会出现什么情况?死锁,会出现相互等待的情况,只会输出第一个NSLog,遇到这种情况我们怎么解决才好呢?2把不同的锁即可解决问题,就是otherMutexTest里面一把锁,otherMutexTest2里面另一把锁就可以解决了,这个我就不截图了,我们可以自己试试

再看下面另一种情况:出现递归怎么办?如下图

遇到上面的这种情况我们又怎么处理,如果就像截图那样的话,就会出现休眠等待.如果我们想执行下去,我们就是可以设置锁的类型来解决这个问题:一共3种类型如下

我们只要把锁的类型换成递归锁,立刻就能解决这个问题,我们加一个递归停止条件,不然会一直运行

还有一个注意的,这个允许重复加锁,一定是在同一个线程,如果是多个线程的话,就不行.递归锁:允许同一个线程对一把锁重复加锁.

自旋锁、互斥锁汇编分析

自旋锁:一直忙等,占用CPU内存,一直在执行代码;互斥锁:不等待,休眠.不执行代码.我们怎么去证明这个问题呢?我们可以从汇编实现上去证明这个问题,我们先看OSSpinLock自旋锁:

首先我们如果用这个上面的来调试的话,是看不出来什么效果的,因为这里面都是一大段一大段汇编代码执行的,我们需要一句一句的执行汇编指令.就需要敲si,s是step的意思,代码一行一行的执行,如果只用s的话,就是一行oc代码执行,一行oc对应可能一大段汇编,所以我们还需要加i,i是instruction的意思是一行一行汇编指令执行,简称si. 还有个是nexti,它也是一行一行汇编指令执行,只是nexti它是遇到函数就会一下执行过去.因为我们要看函数实现,所以我们用si.

我们再看一下,我代码是怎么写的:

我是创建了10个线程去执行卖票,而且在卖票中间sleep(100),这样是为了,第一条线程进去,我们不管,我们主要看第二条线程在这等待的时间,到底做了什么事.所以我们主要看第二条的汇编代码.sleep(100)是为了时间长点,方便我们能看出做什么事.如果时间太短,直接第二条线程就不等待,那我们就看不到效果,请看下面的结果

从上面的结果看,进入OSSpinLockLock函数,它会一直在81aef那里一直循环执行,这是外循环,我们所说的自旋锁就是这样,一直循环执行,占用CPU内存.一旦有人放开这把锁就会条件循环结束,不会再执行循环.

接下来我们看看互斥锁pthread_mutex

查找的方法和上面的一样,我就截图最关键的图即可,请看下面:

执行到最后,直接是callsys,调用系统的方法,是不是类似我之前说的runloop里面的休眠的方法,而且我们知道休眠是任何事情都不会做,不占用CPU内存,所以我们最后看到,我的模拟器立刻又弹出来了,说明确实是睡眠,不占用任何CPU内存.

os_unfair_lock_lock我们可以用上面的方法尝试,它的结果也是互斥锁.

由于锁内容较多,我会在接下来的博客继续介绍

接下来我会继续努力编写其他博客,您的支持就是我最大的动力!

如果觉得我写得对您有所帮助,请点赞关注我,我会持续更新

你了解多线程自旋锁、互斥锁、递归锁等锁吗?相关推荐

  1. 多线程怎么保证数据安全_Python threading实现多线程 提高篇 线程同步,以及各种锁...

    本文主要讲多线程的线程之间的资源共享怎么保持同步. 多线程基础篇见,木头人:Python threading实现多线程 基础篇 Python的多线程,只有用于I/O密集型程序时效率才会有明显的提高,如 ...

  2. 自旋锁/互斥锁/读写锁/递归锁的区别与联系

    自旋锁 互斥锁 读写锁 递归锁 互斥锁(mutexlock): 最常使用于线程同步的锁:标记用来保证在任一时刻,只能有一个线程访问该对象,同一线程多次加锁操作会造成死锁:临界区和互斥量都可用来实现此锁 ...

  3. iOS开发中自旋和互斥锁的理解以及所有锁的性能比较

    补充: 可以看到除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的.苹果在新系统中已经优化了 pthread_mutex 的性能,所以它 ...

  4. JUC - 多线程之悲观锁、乐观锁,读写锁(共享锁、独享锁),公平非公平锁,可重入锁,自旋锁,死锁(十)

    Java中主要有如下锁 一.悲观锁.乐观锁 悲观锁:当前线程去操作数据的时候,总是认为别的线程会去修改数据,所以每次操作数据的时候都会上锁,别的线程去操作数据的时候就会阻塞,比如synchronize ...

  5. Java中的锁机制 -- 乐观锁、悲观锁、自旋锁、可重入锁、读写锁、公平锁、非公平锁、共享锁、独占锁、重量级锁、轻量级锁、偏向锁、分段锁、互斥锁、同步锁、死锁、锁粗化、锁消除

    文章目录 1. Java中的锁机制 1.1 乐观锁 1.2 悲观锁 1.3 自旋锁 1.4 可重入锁(递归锁) 1.5 读写锁 1.6 公平锁 1.7 非公平锁 1.8 共享锁 1.9 独占锁 1.1 ...

  6. python中gil锁和线程锁_Python线程——GIL锁、线程锁(互斥锁)、递归锁(RLock)...

    GIL锁 ​ 计算机有4核,代表着同一时间,可以干4个任务.如果单核cpu的话,我启动10个线程,我看上去也是并发的,因为是执行了上下文的切换,让看上去是并发的.但是单核永远肯定时串行的,它肯定是串行 ...

  7. 华为应用锁退出立即锁_面试官:你说说互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景...

    前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...

  8. 可重入锁(递归锁) 互斥锁属性设置

    前言: 上一次刷博客的时候,看到了自旋锁,通过学习Linux内核,对自旋锁有了一定的了解.在学习的过程中看到这么一句话--自旋锁是不可递归的.自旋锁不可递归,难道有可以递归的锁?带着这个问题,我们来看 ...

  9. 关抢占 自旋锁_互斥锁、自旋锁、读写锁、悲观锁、乐观锁的应用场景

    前言 生活中用到的锁,用途都比较简单粗暴,上锁基本是为了防止外人进来.电动车被偷等等. 但生活中也不是没有 BUG 的,比如加锁的电动车在「广西 - 窃·格瓦拉」面前,锁就是形同虚设,只要他愿意,他就 ...

最新文章

  1. Xiki:一个开发人员寻求增强命令行界面的能力
  2. 用python写的图形计算器
  3. HDU3966(树链剖分)
  4. 数组中出现次数超过一半的数
  5. 关于Web面试的基础知识点--Html+Css(四)
  6. 反射机制及其应用场景
  7. sqluldr2支持mysql吗_sqluldr2工具使用方法
  8. C#POP3协议实现SSL验证登陆GMAIL
  9. 【实时语音转文本】PC端实时语音转文本(麦克风外音系统内部音源)
  10. 微信小程序-创建自己的小程序帐号
  11. 使用Servlet和JSP开发Java应用程序 ----错误处理
  12. 如何将WPS中新建表格下的空白行去掉?
  13. 碟评--梁紫丹新碟<<紫色的声音>>
  14. ffmpeg剪切视频的python小脚本
  15. CImage图像旋转与缩放
  16. 工业互联网WMS系统源码:springboot+ VUE(小程序端+调试视频+部署文档)
  17. 职称计算机考试IE浏览器的使用
  18. LDAP/AD到底是什么?
  19. ES之Highlight及Suggest搜索推荐详解
  20. 【linux docker】ERROR: Failed to Setup IP tables: Unable to enable SKIP DNAT rule: 报错处理

热门文章

  1. Selenium Chrome版本与chromedriver兼容版本对照表
  2. 会议室预定管理系统_企业应用会议室智能预约管理系统有哪些好处
  3. mysql db2 性能_确定DB2 Java应用程序的性能问题
  4. CentOS 8 中安装配置FreeRADIUSDaloRADIUS以及为不同SSID验证MAC等
  5. u盘传输过程数据文件丢失如何恢复
  6. 你必须要知道的JavaScript数据结构与面试题解答
  7. 【愚公系列】2022年03月 微信小程序-富文本编辑器
  8. ubuntu下gEDA安装及使用
  9. html字体颜色反色,根据背景颜色反转CSS字体颜色
  10. matlab中图像边缘提取,Matlab图像处理(五)——图像边缘提取