iOS中的队列分为两类,串行队列(serial queue)和并发队列(concurrent queue).串行队类会按照先进先出的原则来调度任务,在前一个任务没有执行结束之前,后加入队类的任务不会被调度;而对于并发队列来讲可以同时调度多个任务,默认情况下各个任务之间没有必然的依赖关系,任务执行顺序依据系统调度而定,没有必然的先后顺序.添加任务的方式也有两类,同步添加(dispatch_sync)和异步添加(dispatch_asyn),使用同步方式添加任务不会开辟新的线程,队类中的任务都只能使用当前线程来执行;而使用异步方式添加,可以开辟新的线程来执行队类中的任务.按照排列组合他们有四种搭配:
- 串行队列+同步任务
- 并发队列+异步任务
dispatch_queue_t serialQueue = dispatch_queue_create("com.ericydong.queueDemo", DISPATCH_QUEUE_SERIAL); NSLog(@"serialQueue begin:%@", [NSThread currentThread]); __block int i = 0; for (i = 0; i < 5; i++) { NSString *title = [NSString stringWithFormat:@"%03d", i]; dispatch_sync(serialQueue, ^{ NSLog(@"task%@ : %@", title, [NSThread currentThread]); }); } NSLog(@"serialQueue end:%@", [NSThread currentThread]); dispatch_queue_t concurrenrQueue = dispatch_queue_create("com.ericydong.queueDemo", DISPATCH_QUEUE_CONCURRENT); NSLog(@"concurrenrQueue begin:%@", [NSThread currentThread]); for (;i < 10; i++) { NSString *title = [NSString stringWithFormat:@"%03d", i]; dispatch_sync(concurrenrQueue, ^{ NSLog(@"task%@ : %@", title, [NSThread currentThread]); }); } NSLog(@"concurrenrQueue end:%@", [NSThread currentThread]);
输出结果:
begin:<NSThread: 0x283e77500>{number = 1, name = main} task000 : <NSThread: 0x283e77500>{number = 1, name = main} task001 : <NSThread: 0x283e77500>{number = 1, name = main} task002 : <NSThread: 0x283e77500>{number = 1, name = main} task003 : <NSThread: 0x283e77500>{number = 1, name = main} task004 : <NSThread: 0x283e77500>{number = 1, name = main} serialQueue end:<NSThread: 0x283e77500>{number = 1, name = main} concurrenrQueue begin:<NSThread: 0x283e77500>{number = 1, name = main} task005 : <NSThread: 0x283e77500>{number = 1, name = main} task006 : <NSThread: 0x283e77500>{number = 1, name = main} task007 : <NSThread: 0x283e77500>{number = 1, name = main} task008 : <NSThread: 0x283e77500>{number = 1, name = main} task009 : <NSThread: 0x283e77500>{number = 1, name = main} concurrenrQueue end:<NSThread: 0x283e77500>{number = 1, name = main}
使用同步方式添加任务时:
- 不会开辟新的线程,任务都在当前线程中执行;
- 由于执行同步任务并没有开辟新的线程,所以队列中任务执行时会卡顿当前线程,等待任务执行结束之后才能执行后边的操作;
- 任务按照添加顺序先后执行,后添加的任务要等先添加的任务执行结束返回之后才能继续执行;
- 串行队列+并发任务
- 并行任务+并发任务
dispatch_queue_t serialQueue = dispatch_queue_create("com.ericydong.queueDemo", DISPATCH_QUEUE_SERIAL); NSLog(@"serialQueue begin:%@", [NSThread currentThread]); __block int i = 0; for (i = 0; i < 5; i++) { NSString *title = [NSString stringWithFormat:@"%03d", i]; dispatch_async(serialQueue, ^{ NSLog(@"task%@ : %@", title, [NSThread currentThread]); }); } NSLog(@"serialQueue end:%@", [NSThread currentThread]); dispatch_queue_t concurrenrQueue = dispatch_queue_create("com.ericydong.queueDemo", DISPATCH_QUEUE_CONCURRENT); NSLog(@"concurrenrQueue begin:%@", [NSThread currentThread]); for (;i < 10; i++) { NSString *title = [NSString stringWithFormat:@"%03d", i]; dispatch_async(concurrenrQueue, ^{ NSLog(@"task%@ : %@", title, [NSThread currentThread]); }); } NSLog(@"concurrenrQueue end:%@", [NSThread currentThread]);
输出结果:
serialQueue begin:<NSThread: 0x28189f080>{number = 1, name = main} serialQueue end:<NSThread: 0x28189f080>{number = 1, name = main} concurrenrQueue begin:<NSThread: 0x28189f080>{number = 1, name = main} concurrenrQueue end:<NSThread: 0x28189f080>{number = 1, name = main} task000 : <NSThread: 0x2818f4480>{number = 3, name = (null)} task001 : <NSThread: 0x2818f4480>{number = 3, name = (null)} task002 : <NSThread: 0x2818f4480>{number = 3, name = (null)} task003 : <NSThread: 0x2818f4480>{number = 3, name = (null)} task004 : <NSThread: 0x2818f4480>{number = 3, name = (null)} task005 : <NSThread: 0x2818f4480>{number = 3, name = (null)} task007 : <NSThread: 0x2818f4480>{number = 3, name = (null)} task006 : <NSThread: 0x2818f6340>{number = 4, name = (null)} task009 : <NSThread: 0x2818f6340>{number = 4, name = (null)} task008 : <NSThread: 0x2818f4480>{number = 3, name = (null)}
如果你愿意多运行几次,会发现每次的结果都不太一样,但是除了两组begin和end想对任务不发生变化之外,还可以发现如下规律:
- 异步方式添加的任务,会开辟线程。不同的是在串行队列中,无论异步添加多少个任务,都只会开辟一个线程来执行这些任务;而在并发队列中,可以开辟多个新线程来执行任务,具体开辟线程数量依据任务复杂度和系统开销等由系统确定,至少会开辟一条新线程(相对当前添加任务线程);
- 由于开辟了新的线程,所以队列中任务的执行不会卡顿当前的线程;
- 串行队列中添加异步任务,由于只有一条执行任务的线程,所以任务只能按照添加的顺序依次执行;并发队列中添加异步任务,由于可能会开辟多条线程来执行任务,执行各个任务的线程可能不相同,所以任务执行的顺序是随机的.
常见死锁
- 串行队列添加同步任务造成的死锁
在iOS开发中,使用最多的串行队列就是主队列,执行主队列中任务的线程是主线程.而在主队列中添加同步任务就会造成循环等待的死锁现象,这是最常见的死锁现象之一.
在主线程上,在主队列上同步添加任务,例如:
- (void)viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{ NSLog(@"这里的代码是不会执行的"); }); }
这样就会造成死锁,原因:
在串行队列中只有一个执行任务的线程,任务会按照添加的顺序依次执行,不能多个任务同时执行。对于上边的执行代码而言,任务viewDidLoad是早于block任务块被添加到队列中的,所以线程必须先执行任务viewDidLoad,而对于dispatch_sync来讲,block任务块必须要先执行完才能返回,可是这时候只有一个可以执行任务的线程,两个任务都要求先执行自己才能执行其他的任务,就造成了相互等待的死锁.
事实上,不仅主队列具有这样的特性,其他的普通串行队类也具有同样的特性.
在手动创建的串行队列中添加同步任务,并不会造成死锁.虽然主队列也是串行队列,但还是与我们手动创建的串行队列不完全一样,我们手动创建的队列,默认队列里是没有添加任何任务的,需要手动添加任务之后才会有任务,而主队列由系统创建并被系统默认添加任务以及处理用户交互等事件,所以我们添加同步任务时,会导致相互等待。而直接在手动创建的串行队列中同步添加任务是不会造成死锁的:
dispatch_queue_t serialQueue = dispatch_queue_create("com.ericydong.queueDemo", DISPATCH_QUEUE_SERIAL); dispatch_sync(serialQueue, ^{ NSLog(@"不会堵塞"); });
但是如果在自定义的串行队列中,已经添加了任务(无论是同步任何还是一步任务),然后再在任务执行过程中添加同步任务:
dispatch_queue_t serialQueue = dispatch_queue_create("com.ericydong.queueDemo", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ //使用dispatch_sync效果相同 {//这个{}为block1 NSLog(@"thread === %@", [NSThread currentThread]); dispatch_sync(serialQueue, ^{ {//这个{}为block2 NSLog(@"这里的代码是不会执行的"); } }); } });
同样会由于相互等待而造成死锁,原因:
异步任务block1块先被添加到队类中,按照FIFO(先进先出)的原则,必须要等任务block1先执行完才能执行队列中的其它任务.然后使用dispatch_sync向同步队列中添加新的任务block2,dispatch_sync要求必须先执行任务block2块之后才能返回,此时任务block1块并没有执行结束。所以根据串行队列执行任务的特点,任务block1块必须先执行结束才能执行任务block2,而dispatch_sync要求任务block2块执行完之后才能返回继续执行任务block2块,从而造成死锁.
- 使用信号量造成的死锁
有时候我们需要堵塞线程来等待异步请求的结果,这时候如果我们使用了信号量机制来实现这个"同步"过程就有可能会造成死锁.
例如你在主线程中添加了这样的代码:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.responseSerializer = [AFHTTPResponseSerializer serializer]; __block id _responsebject = nil; dispatch_semaphore_t sema = dispatch_semaphore_create(0); [manager GET:url parameters:@{} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { _responsebject = responseObject; dispatch_semaphore_signal(sema); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); NSLog(@"_responsebject == %@", _responsebject);
这样就形成了死锁:
使用dispatch_semaphore_wait会将当前主线程卡顿,等待信号量不为0时,才解除卡顿恢复线程活跃继续执行任务,否则这个线程时不会执行新的任务的.可是AFN中的请求结果回调默认是在回到主线程执行的,由于主线程被卡顿,回调中的所有操作都不能被执行,而回调中的dispatch_semaphore_signal不执行,主线程的等待就不会结束,所以主线程就会一直卡顿.
解决这个问题,可以通过设置新的回调队列,使回调不再主队列中执行:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.responseSerializer = [AFHTTPResponseSerializer serializer]; manager.completionQueue = dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_HIGH); __block id _responsebject = nil; dispatch_semaphore_t sema = dispatch_semaphore_create(0); [manager GET:url parameters:@{} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { _responsebject = responseObject; dispatch_semaphore_signal(sema); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); NSLog(@"_responsebject == %@", _responsebject);
或者开辟新的线程来下载,然后在结果回调中使用主线程进行操作:
dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_HIGH), ^{ AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; manager.responseSerializer = [AFHTTPResponseSerializer serializer]; __block id _responsebject = nil; dispatch_semaphore_t sema = dispatch_semaphore_create(0); [manager GET:url parameters:@{} progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { _responsebject = responseObject; dispatch_semaphore_signal(sema); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { dispatch_semaphore_signal(sema); }]; dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); NSLog(@"_responsebject == %@", _responsebject); });