有这样一个例子,即在主线程开启同步任务死锁的例子:
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
关于这个例子如何会死锁,网上也有很详细的解释。不过可能对于某些基础不是很扎实的同学来说,有些地方不太容易理解。这里,我说一下自己的理解,希望对你有所帮助。
如大家所说,造成这种死锁的原因在于:
1.dispatch_sync,同步执行;
2.dispatch_get_main_queue(),主队列。这里先说一下为什么会造成死锁,后面再介绍其他内容;
第一点,先让我们来看看dispatch_sync和dispatch_async,按照字面意思理解前者是同步的,后者是异步的。苹果给出的文档中,dispatch_sync的解释是:Submits a block for synchronous execution on a dispatch queue。翻译之后就是,向队列中,提交一个同步执行的block。同时,文档中也有这样一句话:dispatch_sync() will not return until the block has finished。就是说,只有当block中的内容执行完之后,才会返回之前插入的地方继续执行。
相对应的,dispatch_async的解释是:Submits a block for asynchronous execution on a dispatch queue。翻译之后就是,向队列中,提交一个异步执行的block。同样的,此时不会等待block的执行,会直接执行之后的代码,而将block交给其他线程。这里暂且不说,后面再聊。
第二点,dispatch_get_main_queue(),苹果给出的解释是:Returns the default queue that is bound to the main thread。也就是说,它返回了依靠主线程来执行任务的队列。这里涉及到Runloop,简单理解就是,iOS程序有一个一直在执行的线程,这个线程会一直运行直到被叫停。这个线程和主队列是绑定的,就是用来执行主队列的任务。
那么现在把它们放在一起考虑,系统一直在顺序执行主队列的任务(通过主线程),此时阻塞主线程(dispatch_sync)向主队列队尾添加一个任务(不知道主队列此时有没有任务在执行,不care)。dispatch_sync必须等到block执行完才会返回当前线程(主线程)继续往下执行,当然下一个任务仍然来自于主队列。那么,此时主线程在等待主队列给出下一个任务(因为主线程与主队列是绑定的,只能根据FIFO原则顺序执行主队列的任务);可是主队列也在等待,它在等待主线程将block执行完成才会给主线程另外一个任务。主线程和主队列在互相等待,那么就造成了死锁。
这个原理确实很绕口,在看了很多博客之后,总算有点儿眉目。本来我是无法理解为什么会造成死锁的,直到想通了上面关节,就是主线程和主队列互相等待。在想通这些的过程中,我做了另外的工作来证实这种想法,或者说另外的这些让我想通了这个关节。让我们来看另外两个例子。
这里有一个不会死锁的例子:
NSLog(@"1,NSThread:%@",[NSThread currentThread]); // 任务1
dispatch_async(queueC, ^{
NSLog(@"2,NSThread:%@",[NSThread currentThread]); // 任务2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3,NSThread:%@",[NSThread currentThread]); // 任务3
});
NSLog(@"4,NSThread:%@",[NSThread currentThread]); // 任务4
});
NSLog(@"5,NSThread:%@",[NSThread currentThread]); // 任务5
输出结果是:
在这个例子中,用到了 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"3,NSThread:%@",[NSThread currentThread]); // 任务3 });
然而并没有出现线程死锁现象,控制台正常打印了1 5 2 3 4 。那么为什么这里不会造成死锁呢?原因就在于dispatch_sync是阻塞了当前线程来给后面队列添加任务。也就是说,在这里,dispatch_sync阻塞了number = 3 的线程,将block添加入主队列,之后由主线程(与主队列绑定)执行打印任务。接着完成之后,再由*number = 3 *的线程执行任务4,那么当然不会造成线程死锁。
下面有一个会死锁的例子:
dispatch_queue_t queueS = dispatch_queue_create("com.demo.serialQueue", DISPATCH_QUEUE_SERIAL); // 串行队列
NSLog(@"1,NSThread:%@",[NSThread currentThread]); // 任务1
dispatch_async(queueS, ^{
NSLog(@"2,NSThread:%@",[NSThread currentThread]); // 任务2
dispatch_sync(queueS, ^{
NSLog(@"3,NSThread:%@",[NSThread currentThread]); // 任务3
});
NSLog(@"4,NSThread:%@",[NSThread currentThread]); // 任务4
});
NSLog(@"5,NSThread:%@",[NSThread currentThread]); // 任务5
它的输出是:
我们看到,它在执行到
dispatch_sync(queueS, ^{ NSLog(@"3,NSThread:%@",[NSThread currentThread]); // 任务3 });
的时候,出现了死锁,回头看一下它的打印结果,任务1和任务5都是主线程执行的,而任务2是number = 3 的线程执行的,也就是说,dispatch_sync阻塞了number = 3 的线程,同时,这个线程执行队列queueS里面的任务**。这就等价于在主线程向主队列同步插入block造成死锁,因为线程和队列相互等待。
弄清楚线程和队列的关系,这个问题就变得很简单了不是吗?