iOS中关于死锁的一点总结
起因
所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
例如:
死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
GCD导致死锁的例子
比如下面的这个会产生死锁的例子:
1 | - (void)viewDidLoad |
你可以自己先分析一下该程序产生死锁的原因。
我们首先要搞清楚同步&异步
串行&并发
这两组基本概念:
同步执行:比如dispatch_sync,这个函数会把一个block加入到指定的队列中,而且会一直等到执行完blcok,这个函数才返回。因此在block执行完之前,调用dispatch_sync方法的线程是阻塞的。
异步执行:一般使用dispatch_async,这个函数也会把一个block加入到指定的队列中,但是和同步执行不同的是,这个函数把block加入队列后不等block的执行就立刻返回了。
接下来是”串行和并行”:
串行队列:比如dispatch_get_main_queue,这个队列中所有任务,一定按照先来后到的顺序执行。不仅如此,还可以保证在执行某个任务时,在它前面进入队列的所有任务肯定执行完了。对于每一个不同的串行队列,系统会为这个队列建立唯一的线程来执行代码。
并发队列:比如dispatch_get_global_queue,这个队列中的任务也是按照先来后到的顺序开始执行,注意是开始,但是它们的执行结束时间是不确定的,取决于每个任务的耗时。对于n个并发队列,GCD不会创建对应的n个线程而是进行适当的优化。
理解清楚上面的两组概念后我们再来分析上面例子产生死锁的原因:
该程序中在主线程中同步执行dispatch_sync(queue,block)
任务,而该任务又被分发到了主线程dispatch_get_main_queue()
中,这就产生了问题。程序在执行dispatch_sync(queue,block)
时,会阻塞调用线程(在这里是主线程),等待block中的任务执行完后再返回阻塞线程继续进行执行,但是block中的任务在执行的时候又需要在主线程中执行,而现在主线程被阻塞,所以就会产生两者相互等待的情况,死锁就形成了。
下面对比着总结一下dispatch_sync
和dispatch_async
的执行流程:
(1)dispatch_sync的执行流程:
- 将block添加到queue队列中
- 阻塞调用线程,等待block()执行结束,回调到调用线程。
(2)dispatch_async的执行流程:
- 将block添加到queue队列中
- 直接回到调用线程(不阻塞调用线程),异步执行。
小结
下面我总结一下关于死锁的一些注意事项:
- 异步执行一定不会产生死锁,因为异步的执行,block会立刻返回,不会阻塞线程。所以在我们开发过程中如果遇到死锁的问题,很大可能是同步执行的原因,这样可以帮助我们快速定位。
- 不要把block任务同步派发到调用gcd所在线程的关联队列中。