- (void)testTimer { // mainThread
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_sync(queue, ^{
NSLog(@"1");
dispatch_sync(queue, ^{
[self performSelector:@selector(log2) withObject:nil afterDelay:0];
});
dispatch_async(queue, ^{
[self performSelector:@selector(log2) withObject:nil afterDelay:0];
});
NSLog(@"3");
});
}
- (void)log2 {
NSLog(@"2");
}
在技术群中看到上面这道题,问输出结果是什么?
仔细看了一下,感觉挺有意思的,仅以记录
下面来解析一下这道题都考了那些东西
1、队列与线程
2、同步与异步
3、死锁的触发条件
4、performSelector:withObject:afterDelay:方法
5、线程保活
先从第一点看
I. 队列与线程
iOS中有四种队列,串行队列、并发队列、主线程队列、全局队列。
主队列是一个特殊的串行队列,主队列的任务都在主线程来执行,专门负责调度主线程度的任务,是无法开辟新的线程。
全局队列是一个特殊的并发队列。
注意,不要认为串行队列中的所有任务都在同一个线程中执行,串行队列中的异步任务,可能会开启新线程去执行。
II. 同步与异步
同步(sync):任务一个接着一个,前一个没有执行完,后面不能执行,没有创建新线程的能力。
异步(async):开启多个新线程,任务同一时间可以一起执行。异步有开启新线程的能力,但是不一定会开启新线程(在主队列上就不会开新线程)。严格来说,异步才有多线程的概念,是多线程的代名词。
III. 死锁
死锁的触发条件是 在某一个串行队列中,同步的向这个串行队列中添加任务。
queue是并发队列,所以最外层block是不会触发死锁的,并发队列中的所有任务可以同时执行,所以在这个队列上执行的任务也不会触发死锁。然后同步block是不会开辟线程的,所以这个block中依然是main thread,但是是在global_queue上处理的,所以会输出1
紧跟着又是一个同步任务,所以这个block中也是在主线程中触发的。
III. performSelector:withObject:afterDelay: 这个方法是一个延迟任务,会在当前线程的runloop中添加一个定时器,时间到了才会执行selector中的任务。
第一个方法是在主线程中触发的,主线程创建的时候会默认创建对应的runloop,所以这个方法对应的sel是会触发的,但是这个方法是个延迟任务,方法会晚一点点触发。
dispatch_async(queue, ^{
[self performSelector:@selector(log2) withObject:nil afterDelay:0];
});
这第二个方法是在当前队列添加的异步任务,会开辟一个子线程来执行任务,但子线程创建的时候,默认是不会创建对应的runloop的,只有用到的时候才会自动创建,当然如果线程中并没有任何事件(source、timer、observer)的话,也不会成功的开启。所以 异步任务会触发,但是这个延迟任务sel是不会触发的,
所以这道题的输出结果是:132
针对死锁做一点延伸:
死锁的触发条件是 在某一个串行队列中,同步的向这个串行队列中添加任务。
1、只会发生在串行队列。并行队列任务是同步执行的,不会阻塞当前线程
2、只会发生在添加同步任务。异步任务会开辟新的线程,不会阻塞当前线程
问:向主线程的串行队列中添加同步任务一定会触发死锁吗?
dispatch_queue_t queue = dispatch_queue_create(@"serial.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue,^(void){
NSLog(@"1");
});
NSLog(@"2");
答案是不一定啊,如上面代码,2是在主线程的主队列上执行输出的,而1的输出任务,是添加到串行队列serial.queue上的。并不会阻塞主线程,所以会立刻执行串行队列serial.queue上的任务,然后再去执行主队列上的任务,所以输出是:12