在上篇文章多线程中,我们简单介绍了下3种创建多线程方法的区别。在一般公司的面试当做,大多也只会问下他们的区别。
请看下面这道面试题,输出结果会是什么了?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
是不是看着有点眼熟,但又有点不确定。因为我们平时中都是先异步,再同步,如下这样写的
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
这样看着是不是舒服多了,那么这种输出结果已经是什么了?
线程卡死.png运行下就会发现,报异常了。是不是觉得奇怪, 好像平时这样写代码也没有啥大问题啊。
再仔细回想、仔细观察下你会发现异步那个地方和我们平时写的不一样
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
这种应该是我们之前经常写的,一点问题都没有。那么问题在哪里了?
我们先看个例子,运行正常
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
再看个例子,也会发现能正常运行。
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2");
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
对比这4个例子,猜测应该是如下这句代码有问题
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
我们单独把它提取出来,运行发现果然是有问题。
同步任务、串行队列卡死.png我们运行下最开始的那道面试题,错误一样。
面试题运行结果.png好了,找到出问题的点了,就是在同步线程里面执行dispatch_get_main_queue()时会发送线程卡死的现象。
我们都是在主队列中去更新UI,即我们的界面都是由系统在主队列中维护的,我们在viewDidLoad方法中使用如下GCD方法
dispatch_sync(dispatch_get_main_queue(), ^{
});
就会出错,我们知道同步线程不会去创建新的线程。
验证如下
- (void)viewDidLoad {
[super viewDidLoad];
for (int i = 0; i <10; i++)
{
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@" %@", [NSThread currentThread]);
});
}
}
输出如下,会看到为同一个线程
2017-08-28 16:28:00.431 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
2017-08-28 16:28:00.432 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
2017-08-28 16:28:00.434 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
2017-08-28 16:28:00.434 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
2017-08-28 16:28:00.434 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
2017-08-28 16:28:00.435 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
2017-08-28 16:28:00.435 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
2017-08-28 16:28:00.435 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
2017-08-28 16:28:00.435 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
2017-08-28 16:28:00.436 测试Demo[68568:1922504] <NSThread: 0x608000076b00>{number = 1, name = main}
所以我们是和UI在同一个线程里面,并且再次去执行一个主队列任务。我们知道dispatch_get_main_queue()这个主队列是一个串行的队列,而串行队列的基础法则时FIFO(先进先出)。所以这时我们就找到了卡死的问题点了:系统维护的dispatch_get_main_queue()这个队列里面在执行viewDidLoad方法,在viewDidLoad中又再次在dispatch_get_main_queue()这个相同的队列里面执行block方法。由于串行队列FIFO原则,系统维护的dispatch_get_main_queue()先进栈,所以要先执行完毕后,再执行后进栈的队列任务,而系统维护的dispatch_get_main_queue()执行完的条件时viewDidLoad方法执行完毕,所以系统维护的dispatch_get_main_queue()会等待dispatch_sync调用的dispatch_get_main_queue()执行完毕,dispatch_sync调用的ispatch_get_main_queue()又在等待先进栈的系统维护的dispatch_get_main_queue()执行完毕,这样就陷入死循环。
所以我们得出一个结论:在同一个线程里面,调用同一个串行队列会发送死锁现象。
现在我们实验下
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t q = dispatch_queue_create("hhhhh", DISPATCH_QUEUE_SERIAL);
dispatch_async(q, ^{
NSLog(@" %@----111", [NSThread currentThread]);
dispatch_sync(q, ^{
NSLog(@" %@----2222", [NSThread currentThread]);
});
});
}
运行解决正如我们所总结的那样
结论实验.png
我们现在实验下并行队列
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t q = dispatch_queue_create("hhhhh", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(q, ^{
NSLog(@" %@----111", [NSThread currentThread]);
dispatch_sync(q, ^{
NSLog(@" %@----2222", [NSThread currentThread]);
});
});
发现能正常运行
实验验证.png最后总结下:在一个线程内部包含同步线程+串行队列一起使用时要注意死锁。
网友评论