GCD之死锁和同步异步函数解析(附带面试题)
一 死锁
1.1 死锁产生的原因
我们根据死锁的提示:__dispatch_wait_for_queue查找到GCD源码中死锁的源码:
我们查找其中关于_dq_state_drain_locked_by的函数调用:
由此,我们可以知道,当线程和队列相同时就会发生死锁。
1.2 死锁实例
//1 //死锁:在当前线程(与当前线程相关的)中同步的向串行队列中添加任务,就会死锁 dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL); //sync的线程是主线程 dispatch_sync(t, ^{ //sync的线程也是主线程 dispatch_sync(dispatch_get_main_queue(), ^{ //均为开启子线程 }); });//主线程死锁
//2 //同样死锁 dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL); dispatch_sync(t, ^{ dispatch_sync(t, ^{ }); });//主线程死锁
//3 //不会死锁 dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL); dispatch_async(t, ^{
//添加任务的线程是async的 队列的线程是主线程 dispatch_sync(dispatch_get_main_queue(), ^{ }); }); NSLog(@"1");
//4 //死锁: //async开启关于t的子线程 dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL); dispatch_async(t, ^{ //sync在t的子线程中添加任务 //t队列是关于t的线程 dispatch_sync(t, ^{ }); });
//5 //不会 dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL); dispatch_sync(t, ^{ <#code#> });
1.3 死锁总结:
1.死锁需要有两个必要条件:同步和串行 2.添加任务的代码执行的线程和串行队列所对应的线程是同一个线程时就会死锁
二、同步函数
我们找到源码中同步函数的实现:
通过层层查找,我们找到了最后调用的函数_dispatch_sync_f_inline函数,其中参数的func就是block任务,可以看到根据不同种类的队列会调用不同的函数,我们看到func也会传递到不同的函数中。通过对不同函数的调用关系,我们也可以发现,func函数最后会被直接执行,所以我们可以认为同步函数中的任务是会被马上执行的。
三、异步函数
同样的,我们先找到异步函数的定义:
我们可以看到这里block封装成了work
这里是将外面传入的block进行了封存。接着我们退回到刚刚异步函数定义的地方,发现下面还有一个函数的调用:这里我们主要关注qos的调用关系
我们可以发现最后根据不同的种类的队列,执行不同的函数。
四、同步函数异步函数总结
同步函数的特点 1. 阻塞当前线程进行等待,直到当前添加到队列的任务执行完成。 2. 只能在当前线程执行任务,不具备开启新线程的能力。 异步函数的特点 1. 不会阻塞线程,不需要等待,任务可以继续执行。 2. 可以在新的线程执行任务,具备开启新线程的能力。(并发队列可以开启多条子线程,串行队 列只能开启一条子线程)
五、面试题
-(void)test1 { dispatch_queue_t queue = dispatch_queue_create("zxy", DISPATCH_QUEUE_CONCURRENT); //12执行顺序不确定 dispatch_async(queue, ^{ NSLog(@"1"); }); dispatch_async(queue, ^{ NSLog(@"2"); }); dispatch_sync(queue, ^{//立即执行 NSLog(@"3"); }); NSLog(@"0"); //789顺序不确定 但一定在0的后面 dispatch_async(queue, ^{ NSLog(@"7"); }); dispatch_async(queue, ^{ NSLog(@"8"); }); dispatch_async(queue, ^{ NSLog(@"9"); }); }
-(void)test5 { //不会死锁 dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_SERIAL); NSLog(@"1"); dispatch_sync(t, ^{ NSLog(@"2"); dispatch_async(t, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); } //1 2 4 3(3一定在4的后面 因为队列是串行的) 5
-(void)test2 { dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); dispatch_sync(t, ^{ NSLog(@"2"); dispatch_async(t, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); } //12(3不确定 但在2后面)45
-(void)test6 { dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); dispatch_async(t, ^{ NSLog(@"2"); dispatch_sync(t, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); } //1 2 3 4(5不确定)
-(void)test7 { dispatch_queue_t t = dispatch_queue_create("lg", DISPATCH_QUEUE_CONCURRENT); NSLog(@"1"); dispatch_sync(t, ^{ NSLog(@"2"); dispatch_sync(t, ^{ NSLog(@"3"); }); NSLog(@"4"); }); NSLog(@"5"); } //12345
- (void)test3 { self.num = 0; while (self.num < 100) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ //while循环卡住了主线程 self.num ++; }); } NSLog(@"self.num = %d",self.num); } //答案是比100大一点点 - (void)test4 { self.num = 0; for (int i = 0; i < 100; i ++) { dispatch_async(dispatch_get_global_queue(0, 0), ^{ //这里子线程++ self.num ++; }); } //主线程输出 NSLog(@"self.num = %d",self.num); } //答案比100小点