多线程的解决方案
GCD、NSOperation、NSThread,performSelectorInBackground: withObject:也可以开启一个异步线程
GCD
- 同步/异步和串行/并发
① 同步串行dispatch_sync(serial_queue, ^{...})
;
② 异步串行dispatch_async(serial_queue, ^{...})
;
③ 同步并行dispatch_sync(concurrent_queue, ^{...})
;
④ 异步并行dispatch_async(concurrent_queue, ^{...})
;- 死锁产生的原因
队列引起的循环等待- 思考
问题一:- (void)viewDidLoad { [super viewDidLoad]; //会产生死锁 dispatch_sync(dispatch_get_main_queue(), ^{ [self doSomething]; }); //改为异步线程,可以解决死锁 dispatch_async(dispatch_get_main_queue(), ^{ [self doSomething]; }); //不会产生死锁 dispatch_queue_t serialQueue = dispatch_queue_create("com.test.serialQueue", DISPATCH_QUEUE_SERIAL); dispatch_sync(serialQueue, ^{ [self doSomething]; }); }
这段代码会产生死锁,死锁的原因是因为
viewDidLoad
本就在主线程dispatch_get_main_queue()
,viewDidLoad
的结束依赖于dispatch_sync
线程的完成,而dispatch_sync
被加入dispatch_get_main_queue()
,dispatch_sync
线程的完成需要viewDidLoad
的完成,导致了互相等待,从而产生死锁
。
如果将dispatch_sync
改为dispatch_async
,由于是异步添加,所以不会阻塞主线程来等待[self doSomething]
的执行完成,也就不存在与viewDidLoad
的互相等待了。问题二:
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"1"); dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"2"); dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); }
这段代码输出结果为
2020-04-12 00:17:17.039572+0800 test[14894:3002426] 1 2020-04-12 00:17:17.039684+0800 test[14894:3002426] 2 2020-04-12 00:17:17.039779+0800 test[14894:3002426] 3 2020-04-12 00:17:17.039874+0800 test[14894:3002426] 4 2020-04-12 00:17:17.039969+0800 test[14894:3002426] 5
因为是并发执行,所以并不依赖于上一个线程是否结束,也就不会产生死锁,又因为是同步执行,所以输出结果为顺序输出。
如果将异步队列改为串行队列,就会同问题一一样,产生死锁
问题三:情况 ①:
- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"1"); [self performSelector:@selector(pringLog) withObject:nil afterDelay:0]; NSLog(@"3"); }); } - (void)pringLog{ NSLog(@"2"); }
这段代码输出结果为
2020-04-12 00:30:51.146398+0800 test[14937:3013405] 1 2020-04-12 00:30:51.146624+0800 test[14937:3013405] 3
performSelector:withObject: afterDelay:
方法意为在当前Runloop中延迟0秒后执行selector中方法。 使用该方法需要注意以下事项: 在子线程中调用performSelector: withObject: afterDelay:
默认无效,这是因为performSelector: withObject: afterDelay:
是在当前Runloop中延时执行的,而子线程的Runloop默认不开启,因此无法响应方法。针对情况①,修改为情况 ②:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"1"); [self performSelector:@selector(pringLog) withObject:nil afterDelay:0]; [[NSRunLoop currentRunLoop] run]; NSLog(@"3"); });
加上
[[NSRunLoop currentRunLoop] run];
后会正常输出:2020-04-12 00:55:23.335976+0800 test[15109:3037849] 1 2020-04-12 00:55:23.336468+0800 test[15109:3037849] 2 2020-04-12 00:55:23.336585+0800 test[15109:3037849] 3
情况③:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"1"); [self performSelector:@selector(pringLog)]; NSLog(@"3"); });
如果改为
[self performSelector:@selector(printInfo)];
则会产生死锁,原因是performSelector:
方法会在当前线程同步执行pringLog方法。情况④:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"1"); [self performSelectorInBackground:@selector(pringLog) withObject:nil]; NSLog(@"3"); });
使用performSelectorInBackground:withObject:方法输出结果为:
2020-04-12 01:05:43.912294+0800 test[15135:3046984] 1 2020-04-12 01:05:43.912486+0800 test[15135:3046984] 3 2020-04-12 01:05:43.920925+0800 test[15135:3046990] 2
- dispatch_barrier_async
dispatch_barrier_async(concurrent_queue, ^{写操作});
通过GCD实现多读单写,多读单写要实现:
① 读者、读者并发;
② 读者、写者互斥;
③ 写者、写者互斥
@interface UesrCenter() @property (nonatomic, strong) NSMutableDictionary *userCenterDict; @property (nonatomic, strong) dispatch_queue_t cuncurrent_queue; @end @implementation UesrCenter - (instancetype)init { self = [super init]; if (self) { self.cuncurrent_queue = dispatch_queue_create("com.userCenter.witer.queue", DISPATCH_QUEUE_CONCURRENT); self.userCenterDict = [NSMutableDictionary dictionary]; } return self; } - (id) objectForKey:(NSString *)key{ __block id obj; //同步读取指定数据 dispatch_sync(self.cuncurrent_queue, ^{ obj = [_userCenterDict objectForKey:key]; }); return obj; } - (void)setObject:(id)obj forKey:(NSString *)key{ //异步栅栏调用设置数据 dispatch_barrier_async(self.cuncurrent_queue, ^{ [self.userCenterDict setObject:obj forKey:key]; }); }
- dispatch_group
实现A、B、C三个任务并发执行,完成后执行任务D;@interface GCDGroupTest() @property (nonatomic, strong) NSArray <NSURL *> *urlArray; @property (nonatomic, strong) dispatch_queue_t queue_t; @end @implementation GCDGroupTest - (instancetype)init { self = [super init]; if (self) { self.urlArray = [NSArray arrayWithObject:[NSURL URLWithString:@"***.png"]]; self.queue_t = dispatch_queue_create("CGDGroupTest.queue", DISPATCH_QUEUE_CONCURRENT); } return self; } - (void)handle{ dispatch_group_t group = dispatch_group_create(); for (NSURL *url in self.urlArray){ dispatch_group_enter(group);//手动在group中加入一个任务 dispatch_group_async(group, self.queue_t, ^{ NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"任务%@结束", url); dispatch_group_leave(group);//手动在group中移除一个任务; }]; [task resume]; }); //这里需要注意dispatch_group_async(group,queue,^{...})的结束是以block完成为结束 //如果block中包含一些异步下载等操作,可能并不会达到预期的效果 //所以需要手动dispatch_group_enter(group)和dispatch_group_leave(group)来控制任务是否完成 } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ //队列group都执行完成之后,在dispatch_get_main_queue线程执行任务 NSLog(@"全部下载完成"); }); } @end
NSOperation
NSOperation需要和NSOperationQueue配合使用来实现多线程方案,在没有使用 NSOperationQueue的情况下,单独使用NSOperation期操作操作是在当前线程执行的,并不会开启新线程。
优势及特点
① 添加任务依赖(是GCD和NSThread所不具备的):
② 任务执行状态控制:需要创建子类,重写- (void)main
和- (void)start
方法
③NSOperationQueue
可以控制最大并发量maxConcurrentOperationCount
:默认情况下为-1,表示不进行限制,设为1相当于串行
④ 使用KVO
观察对操作执行状态的更改:如isExecuteing
、isFinished
、isCancelled
。
⑤ NSOperation可添加完成代码块,在操作完成后执行completionBlock
任务执行状态控制
① 如果只重写main方法,底层控制任务状态;
② 如果重写了start方法,需要我们自行控制任务状态。
系统通过KVO
移除一个isFinished = YES
的operation
;
NSThread
结合Runloop来实现常驻线程
多线程与锁
iOS 实现线程加锁有很多种方式。
@synchronized
、NSRecursiveLock
、NSLock
、OSSpinLock
、NSCondition
、NSConditionLock
、pthread_mutex
、dispatch_semaphore_t
常用的有@synchronized
,NSLock
,OSSpinLock
① @synchronized:一般在单例的时候使用,保证创建单例对象时在多线程情况下也是唯一的;
② OSSpinLock:自旋锁,循环等待询问,不释放当前资源,主要用于轻量级数据访问,如简单的int值+1/-1操作;
③ NSLock:需要注意死锁问题,上锁和解锁需要成对出现- (instancetype)init { self = [super init]; if (self) { self.aLock = [[NSLock alloc] init]; [self methodA]; } return self; } - (void)methodA{ [self.aLock lock]; NSLog(@"methodA"); [self methodB]; [self.aLock unlock]; } - (void)methodB{ [self.aLock lock]; NSLog(@"methodB"); [self.aLock unlock]; }
针对上诉NSLock出现的死锁问题,可以使用
NSRecursiveLock
解决
NSRecursiveLock
主要用于方法的递归调用或如上面A->B方法调用,在内部都需要上锁的情况... self.aLock = [[NSRecursiveLock alloc] init]; ...
总结
GCD
:用来实现一些简单的线程同步、子线程的分派,多读单写等
NSOperation
:可以方便我们管理任务的状态,添加/移除依赖等
NSThread
:主要是要实现常驻线程;