这是一篇继上一篇继续介绍多线程同步的博客.(你了解多线程自旋锁、互斥锁、递归锁等锁吗?)
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操作;临界区代码复杂或者循环量大.