GCD--dispatch_get_main_queue()卡死

在上篇文章多线程中,我们简单介绍了下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");
}

这样看着是不是舒服多了,那么这种输出结果已经是什么了?

GCD--dispatch_get_main_queue()卡死_第1张图片
线程卡死.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

我们运行下最开始的那道面试题,错误一样。

GCD--dispatch_get_main_queue()卡死_第2张图片
面试题运行结果.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] {number = 1, name = main}
2017-08-28 16:28:00.432 测试Demo[68568:1922504] {number = 1, name = main}
2017-08-28 16:28:00.434 测试Demo[68568:1922504] {number = 1, name = main}
2017-08-28 16:28:00.434 测试Demo[68568:1922504] {number = 1, name = main}
2017-08-28 16:28:00.434 测试Demo[68568:1922504] {number = 1, name = main}
2017-08-28 16:28:00.435 测试Demo[68568:1922504] {number = 1, name = main}
2017-08-28 16:28:00.435 测试Demo[68568:1922504] {number = 1, name = main}
2017-08-28 16:28:00.435 测试Demo[68568:1922504] {number = 1, name = main}
2017-08-28 16:28:00.435 测试Demo[68568:1922504] {number = 1, name = main}
2017-08-28 16:28:00.436 测试Demo[68568:1922504] {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]);
        });
    });
}

运行解决正如我们所总结的那样


GCD--dispatch_get_main_queue()卡死_第3张图片
结论实验.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]);
        });
    });

发现能正常运行

GCD--dispatch_get_main_queue()卡死_第4张图片
实验验证.png

最后总结下:在一个线程内部包含同步线程+串行队列一起使用时要注意死锁

你可能感兴趣的:(GCD--dispatch_get_main_queue()卡死)