解决线程同步的方案汇总总结

这是一篇继上一篇继续介绍多线程同步的博客.(你了解多线程自旋锁、互斥锁、递归锁等锁吗?)

GNUstep介绍

再介绍其他锁之前我们先看一个其他的知识点.我们知道在Foundation框架下,苹果公开的源码只有NSObject,而我们想知道的NSString、NSArray、NSRunLoop、NSThread等等都是没有给源码的.只给了NSObject的部分实现,如果我们确实想看这些源码的话.通过汇编,打断点一步一步看,这样也是可以看到的,但是这样就有点麻烦,要会汇编语言,而且还要一点一点的调试才能知道.所以这里给大家介绍另一个方案,叫做:GNUstep.

GNUstep是GNU计划的项目之一,GNU计划就是一个软件计划,就是希望能够开源非常非常多的源码,希望都是自由的,我们可以认为这个计划就是做了非常非常多的开源项目.GNUstep就是GNU计划之一,它将Cocoa的OC库重新开源实现了一遍.就是虽然苹果的NSString、NSArray、NSRunLoop、NSThread等等都是没有开源的,GNUstep就是把它重新实现了一遍并且开源了.

GNUstep源码下载地址(建议下载 Base1.26.0版本)

虽然GNUstep不是苹果官方源码,但还是具有一定的参考价值,它里面的实现和苹果源码的实现是非常接近的.请看下面的截图:

它具有一定的参考价值,对于我们后面理解一些东西有很重要的作用.

接下来我们看看条件锁

pthread_mutex (条件锁)

对于mutex的互斥锁、递归锁我们都是很清楚的了,接下来我们直接看看条件锁.首先看一下我的需求是什么样,如下图:

我的业务需求是希望:如果dataMuArr.count==0的时候,我不删除,等dataMuArr有数据了我再去删除.那这时候,我们就可以用条件锁,请看下面:

从log输出,可以看出,条件锁确实可以解决这种需求.而且我们可以发现pthread_cond_wait在休眠的时候是解锁了,所以add方法才能继续执行,被唤醒的时候又加锁,所以加锁和解锁是还是成对出现的.还有个补充点就是:目前是一个线程在等待,如果是多个线程在等待,我们就用pthread_cond_broadcast(&_cond)即可,broadcast是广播的意思,所以很容易理解,大家可以试试.

NSLock、NSRecursiveLock、NSCondition详解

NSLock:它是对pthread_mutex普通锁的封装,一看就是oc对象,使用起来更加面向对象,我们看下用法

- (BOOL)tryLock;//尝试加锁

- (BOOL)lockBeforeDate:(NSDate*)limit; //在这个时间之前,如果我能等到这把锁解锁我就等,否则就不等

- (void)lock;  //加锁

- (void)unlock;//解锁

主要是上面这四个,我们直接用吧,上面写得都很清晰:

上面结果很清晰,没有问题,用法也是非常的简单.这里还有个注意点,我们可以用我上一个博客的知识点可以查看汇编调用过程,你会发现如下

因为这个过程还要找缓存,找方法,全是消息机制那些,知道这些有什么用呢?这个给我们对比线程同步的性能方面提供了参考,它的性能相对来说,肯定没有pthread_mutex效率高,因为它多执行了很多代码,这个很清晰吧.

如果我们通过打断点也是能找到NSLock的实现,具体调用等等,但是发现很麻烦,这时候我们就用上面说的GNUstep里面查找一下,如下图:

这里很明显可以看出是对pthread_mutex普通锁的一个封装.

NSRecursiveLock:它是对pthread_mutex递归锁的封装,它基本和上面的NSLock一样的,几乎是一样的,我们直接看一下用法,稍微过一下:

NSCondition:它是对pthread_mutex和cont的封装,也就是上面的条件锁,我们也看下用法:

- (void)wait;//等待

- (BOOL)waitUntilDate:(NSDate*)limit;//在这个时间之前,如果我能等到这把锁解锁我就等,否则就不等

- (void)signal;//信号

- (void)broadcast;//广播

- (void)lock;  //加锁

- (void)unlock;//解锁

我直接演示一下用法即可,你可以用条件锁,也可以普通锁.我就把前面那个演示一下:

NSConditionLock详解

NSConditionLock:它是对NSCondition的进一步封装,可以设置具体的条件值,以前的锁都是等待什么,这个是可以设置具体的值

这时候如果我们把初始化条件置为其他的话,程序就会一直休眠,不会有任何打印.用这个锁,我们就可以达到控制多线程的执行顺序,无论多少个,我们都能控制.这个也是很明确,就不细说了

dispatch_queue (DISPATCH_QUEUE_SERIAL)

直接使用GCD的串行队列,也是可以实现线程同步的.我们不能想到线程同步,就想到锁,我们要知道线程同步的本质是什么,就是多条线程抢占同一个资源,所以串行队列也是可以解决线程同步的问题.比如我们就拿卖票来说.

dispatch_semaphore_t详解

dispatch_semaphore_t叫做"信号量"

信号量的初始值,可以用来控制线程并发访问的最大数量.如果我们最大数量设置1,那就是能达到线程同步的目的,现在我们先去看一下怎么使用

dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER):如果信号量的值是>0,就让信号量的值减1,然后继续往下执行代码...直到当信号量的值是0的时候,这时候就会休眠等待.首先看你传的时间,如果是now就立即执行,这里传的DISPATCH_TIME_FOREVER就不受时间这个条件限制了,直到当信号量的值>0才会唤起休眠,继续执行.

dispatch_semaphore_signal(self.semaphore):就是让信号量的值+1,只要变成1就会唤起之前的等待,就这样重复执行.

所以我们之前的代码,我们只要把信号量的值设置为1就可以做到线程同步,这里大家可以自己尝试.

@synchronized详解

相信很多都见过这个关键字

@synchronized它是对pthread_mutex递归锁的一个封装,我们参考一下GNUstep去参考一下源码.

它是在写法上最简单的,这里我们先看一下怎么使用:

写法非常简单,也是能解决线程同步. 这里的 @synchronized (self) 里面的self跟每个锁是一一对应的,我们可以理解就是self是key,这个锁就是value,就是存在字典中(可以由GNUstep去参考得知).所以如果是同一锁,@synchronized (self)这里的self对象必须是同一个对象.

说了这么多,上面的都是常用的方案.,当然还有其他方案,

线程同步总结:

1.同步方案性能优化对比:

这里是整理了一个由高到低的排序,也是测出来的,供大家参考:

os_unfair_lock (只支持iOS10以后)

OSSpinLock (不建议使用,iOS10以后弃用)

dispatch_semaphore_t

pthread_mutex 

dispatch_queue (DISPATCH_QUEUE_SERIAL)

NSLock

NSCondition

pthread_mutex (recursive)

NSRecursiveLock

NSConditionLock

@synchronized

所以推荐使用dispatch_semaphore_t和pthread_mutex ,初始化代码多的话,可以定义成宏.

2.自旋锁和互斥锁的对比

(虽然自旋锁现在已经不用了,因为只有一个还是废弃的,但是有时候面试喜欢问,所以这里我们还是说下)

一、什么时候用自旋锁比较划算?

预计线程的等待时间较短;加锁的代码(临界区)经常被调用,但竞争情况很少发生;CPU资源不紧张;多核处理器

二、什么时候用互斥锁比较划算?

预计线程等待时间较长;单核处理器;临界区由IO操作;临界区代码复杂或者循环量大.

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

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

感谢支持🙏🙏🙏!

我是GDCoder,我们下期见!

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值