GCD--dispatch_get_main_queue()卡死

作者: Hardy_Hu | 来源:发表于2017-08-28 17:05 被阅读2475次

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

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

    相关文章

      网友评论

        本文标题:GCD--dispatch_get_main_queue()卡死

        本文链接:https://www.haomeiwen.com/subject/tnkgdxtx.html