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

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

多线程的隐患

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

存钱取钱案例

比如我现在有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我们可以用上面的方法尝试,它的结果也是互斥锁.

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

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

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

感谢支持🙏🙏🙏!

我是GDCoder,我们下期见!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容