主要内容:
1 GCD队列循环等待、多读单写、组任务
2 NSOpertaion优点
3 NSThread实现原理
4 常用锁的区别
GCD
同步串行
在viewDidLoad中有下面一段代码
dispatch_sync(dispatch_get_main_queue(), ^{
[self doSomething];
});
很多人说这会造成死锁。其实更准确的说法是:主队列循环等待造成死锁。
主队列是一个串行队列,viewDidLoad提交到了主线程执还未执行完毕,此时再同步提交doSomething到主线程。viewDidLoad被阻塞等待doSomething返回,而doSomething是后提交的任务,必须等前一个任务viewDidLoad执行完毕后,才能执行。这就是队列循环等待造成的死锁。
在viewDidLoad中代码改为如下
_serialQueue = dispatch_queue_create("thread_name", DISPATCH_QUEUE_SERIAL);
dispatch_sync(_serialQueue, ^{
[self doSomething];
});
这样会不会造成,队列循环等待呢?答案是:不会。
这里需要注意:只要是同步方式提交,不管是提交到串行队列还是并发队列,都是在当前线程执行。
所以,如图所示不会造成死锁,并且doSomething会在主线程中执行。
同步提交到并发队列
NSLog(@"1");
dispatch_sync(globalQueue, ^{
NSLog(@"2");
dispatch_sync(globalQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
并发队列特点:提交到队列的block可以并发执行。 打印结果12345
异步提交到并发队列
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
[self performSelector:@selector(printLog) withObject:nil afterDelay:0];
NSLog(@"3");
});
-(void)printLog
{
NSLog(@"2");
}
结果是:1和3。RunLoop在主线程是自动创建的,在子线程中是没有创建的。performSelector:withObject:afterDelay: 方法关键是afterDelay相当于创建了一个Timer提交到了RunLoop。等待下一次RunLoop循环事件时执行。而子线程中RunLoop根本就没有创建。所以printLog也就不会执行。
[self performSelector:@selector(printLog) withObject:nil];
[self performSelectorOnMainThread:@selector(printLog) withObject:nil waitUntilDone:YES];
请注意上面2个方法就是一个普通方法调用。与带afterDelay的有本质区别。
dispatch_barrier_async()
提供了一种多读单写技术,读与读可以并发,读与写是互斥的,写和写之间是互斥的。
#import "DataCenter.h"
@interface DataCenter()
{
dispatch_queue_t _concurrent_queue;
//用户数据中心,可能有多个线程需要访问
NSMutableDictionary *_dataCenterDict;
}
@end
@implementation DataCenter
-(id)init
{
self = [super init];
if(self){
_concurrent_queue = dispatch_queue_create("read_write_queue", DISPATCH_QUEUE_CONCURRENT);
_dataCenterDict = [NSMutableDictionary dictionary];
}
return self;
}
-(id)objectForKey:(NSString*)key
{
__block id obj;
//同步读取指定数据: 若果是线程A调用,那么子啊线程A中执行。 若果是线程B调用,那么在线程B中执行
//由于是并发队列,可以同时满足多个线程同时调用。
dispatch_sync(_concurrent_queue, ^{
obj = [_dataCenterDict objectForKey:key];
});
return obj;
}
-(void)setObject:(id)obj forKey:(NSString*)key
{
//异步调用栅栏设置数据
dispatch_barrier_async(_concurrent_queue, ^{
[_dataCenterDict setObject:obj forKey:key];
});
}
NSOperation
优点
1 添加依赖任务
2 任务执行状态控制
3 控制最大并发量
任务执行状态控制
isReady : 就绪
isExecuting: 执行中
isFinished: 执行完成
isCancelled: 已取消
只重写main()方法,系统控制任务状态,以及退出
重写了start()方法,需要自己控制任务状态
系统是怎样移除一个isFinished=YES的NSOperation的?KVO
NSThread
实现原理:内部创建了一个pthread线程,当执行完main函数后,系统会自动退出。
锁
@synchronized
一般在创建单例对象的时候使用,保证在多线程环境下创建对象唯一
atomic
属性关键字,对被修饰对象进行原子操作(不负责使用)
@property(atomic)NSMutableArray *array;
self.array = [NSMutableArray array]; //保证线程安全
[self.array addObject: obj]; //不保证线程安全
OSSpinLock
循环等待访问,不释放当前资源, 专门用于轻量级访问,如+1,-1操作。如:引用计数
NSRecursiveLock
递归锁可以重入
NSLock
互斥锁,不可重入,上锁解锁,成对出现。
dispatch_semaphore_t
dispatch_semaphore_create(1);
dispatch_semaphore_wait(semphore,DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphore)
dispatch_semaphore_create()
struct semaphore {
int value;
List //相关线程
}
dispatch_semaphore_wait 检测到S.value < 0 ,主动阻塞自己
dispatch_semaphore_wait()
{
S.value = S.value - 1;
if S.value < 0 then Block(S.List);
}
dispatch_semaphore_signal 检查后,去唤醒线程。对于线程来说,是一个被动唤醒。
dispatch_semaphore_signal()
{
S.value = S.value + 1;
if(S.value <= 0) then wakeup(S.List)
}
总结
1 怎样GCD实现多读单写?
2 iOS提供了几种多线程技术,各自特点是什么?
3 NSOpertaion对象在Finished之后是怎样从queue当中移除的?
4 你多用过那些锁?结合实际谈谈你是怎样实现的?