基础知识
笔者对照阅读了苹果官方的开发者指南和一些博客,发现对于队列、死锁的理解很容易产生一些误区,笔者在下文中将一一祭献出这些理解误区(😈~)
从原理层面理解死锁产生的原因,比反复举例的方法更快,所以基础知识部分一定要看哦
队列 dispatch queue
队列是一个用于管理多个任务的数据结构,按照FIFO的顺序执行block。每个注册到队列的任务都会被分配到系统管理的线程池中执行。
常见误区:串行队列是单线程,并行队列是多线程
这个说法是错误的,不管是串行还是并行队列都会将任务分配给多个不同的线程执行,区别在于串行队列能够保证一次仅执行一个任务,而并行队列一次可执行多个任务
队列类型 | 描述 |
---|---|
串行队列 | 私有队列,一次仅执行一个任务,任务所在的线程由队列管理;常用于顺序读写某一特殊数据资源 |
并行队列 | 全局队列,可同时执行多个任务,任务所在的线程由队列管理 |
主线程队列 | 全局的串行队列,负责在主线程中执行任务;该队列中的任务与应用runloop中的其它事件交错执行 |
常见方法
方法 | 描述 |
---|---|
dispatch_queue_t <strong>dispatch_get_global_queue</strong>(long identifier, unsigned long flags); | 返回一个全局并行队列,identifier确定队列优先级,取值为QOS_CLASS_USER_INTERACTIVE|QOS_CLASS_USER_INITIATED|QOS_CLASS_UTILITY|QOS_CLASS_BACKGROUND,flag暂不使用,设为0 |
dispatch_queue_t <strong>dispatch_queue_create</strong>(const char *label, dispatch_queue_attr_t attr) | 创建一个队列,label是队列的唯一标识符,命名格式为com.example.myqueue;attr参数,DISPATCH_QUEUE_SERIAL表示创建串行队列,DISPATCH_QUEUE_CONCURRENT表示创建并行队列 |
dispatch_queue_t <strong>dispatch_get_main_queue</strong>(void) | 返回主线程队列 |
dispatch_queue_t <strong>dispatch_get_current_queue</strong>(void) | 返回当前队列 |
内存管理
所有的队列都是引用计数的数据类型,一个队列被创建后的初始引用计数为1;但是我们不须要管理全局并行队列,所有队列上retain、release操作都会被忽略;故而不须要存储全局队列的指针变量,每次使用时调用dispatch_get_global_queue方法来获取全局队列。
补充
1.相互独立的队列是并行执行的(这个说法不太准确,应该是相互独立的队列中的任务是可以并行执行的,没有关联关系)
2.系统决定某一时刻并行执行的任务总数量
3.系统决定执行哪个任务时会考虑队列的优先级
4.任务在添加到队列的那一刻即要做好执行的准备
5.私有队列是基于引用计数的对象,每个被加到队列的block都持有指向队列的指针,直到此block执行结束后销毁;故而所有block执行完毕后队列才有可能被系统回收。
dispatch_async&dispatch_sync
常见误区:async并行执行任务,sync串行执行任务
async、sync并不会执行某个任务,二者的差别也没有我们想象的那么大,都是把block添加到队列中,唯一的区别是sync会阻塞线程直到block执行完毕,而dispatch会马上返回。
可以看一下官网给出的定义:
方法 | 定义 |
---|---|
dispatch_async | Submits a block for asynchronous execution on a dispatch queue and returns immediately |
dispatch_sync | Submits a block object for execution on a dispatch queue and waits until that block completes |
方法中的queue参数决定是串行还是并行执行block。
死锁的产生
这是一个最常见的死锁产生的场景:
int main(int argc, const char * argv[]) {
@autoreleasepool {
dispatch_sync(dispatch_get_main_queue(), ^(void){
//block code
});
}
return 0;
}
如果你将这段代码中dispatch_sync方法放到项目的Appdelegate方法中,会发现应用会卡在启动界面。
常见误区:在主线程中添加dispatch_sync,会导致死锁。
这个说法并不准确,官网给出了更加准确(geek)的说法:
Do not call the dispatch_sync function from a task that is executing on the same queue that you pass to your function call. Doing so will deadlock the queue. If you need to dispatch to the current queue, do so asynchronously using the dispatch_async function.
你不应该将block添加到调用它的dispatch_sync方法所在的队列上;以上dispatch方法在主线程队列中调用,同时又将block添加到了dispatch_get_main_queue()返回的主线程队列中,故而造成了死锁。
为什么会这样呢?如果你充分理解了前文中的预备知识,原因是可以推理出来的,这段代码导致了以下事件:
1.dispatch_sync将block添加到主线程队列
2.dispatch_sync阻塞主线程直到block执行完毕
3.线程被阻塞了,block永远无法执行
dispatch_sync多么尴尬,它阻碍了自己的block的执行!