锁是线程编程同步工具的基础。iOS开发中常用的锁有如下几种:
1、@synchronized
2、NSLock 对象锁
3、NSRecursiveLock 递归锁
4、NSConditionLock 条件锁
5、pthread_mutex 互斥锁(C语言)
6、dispatch_semaphore 信号量实现加锁(GCD)
7、 OSSpinLock (暂不建议使用)
比较说明:
@synchronized 关键字加锁 互斥锁,性能较差不推荐使用
@synchronized(这里添加一个OC对象,一般使用self) {
这里写要加锁的代码
}
注意点
1.加锁的代码尽量少
2.添加的OC对象必须在多个线程中都是同一对象
3.优点是不需要显式的创建锁对象,便可以实现锁的机制。
4. @synchronized块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。
NSLock 互斥锁 不能多次调用 lock方法,会造成死锁
NSRecursiveLock 递归锁
使用锁最容易犯的一个错误就是在递归或循环中造成死锁
如下代码中,因为在线程1中的递归block中,锁会被多次的lock,所以自己也被阻塞
NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。
递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。
只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
NSConditionLock 条件锁
条件锁,一个线程获得了锁,其它线程等待。
[xxxx lock];
表示 xxx 期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition) 那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁),则等待,直至其他线程解锁
[xxx lockWhenCondition:A条件];
表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。
[xxx unlockWithCondition:A条件];
表示释放锁,同时把内部的condition设置为A条件
dispatch_semaphore 信号量实现加锁
GCD中也已经提供了一种信号机制,使用它我们也可以来构建一把”锁”(从本质意义上讲,信号量与锁是有区别。):
线程和锁
经典案例:
1.- (void)viewDidLoad {
// 异步主队列
dispatch_async(global_queue,^{
NSLog(@"1");
[self performSelector: @selector(printLog) withObject: nil, afterDelay: 0];
NSLog(@"3");
});
}
- (void)printLog { NSLog(@"2");}
分析:输出结果为13。
global_queue是全局队列,采用dispatch_async,会开辟一个子线程,实际上任务会在GCD底层所维护的线程池当中某个线程中执行处理。子线程的runloop默认是不开启的,而
通过异步方式分派任务到全局并发队列后,会在GCD底层所维护的线程池当中某个线程中执行处理,在GCD底层所维护的线程池中的线程默认不会开启对应的runloop,而performSelector:withObject:afterDelay是在没有runloop的情况下会失效,所以此方法不执行。
2.怎样利用GCD实现多读单写?
分析:需要满足读者和读者并发、读者和写者互斥、写者和写者互斥。
1.读处理之间需要并发的,用到并发队列,因为读取操作,往往需要立刻返回结果,故采用同步。这些读处理允许在对个子线程。2.写处理时其它操作都不能执行。利用栅栏函数异步操作,原因是栅栏函数同步操作会阻塞当前线程,如果当前线程还有其它操作,就会影响用户体验。
多读单写方案:利用GCD提供的栅栏函数。
dispatch_barrier_async(concurrent_queue,^{ //写操作 });