美文网首页iOS 的那些事儿iOS功能开发
深入理解 iOS 中的队列和执行

深入理解 iOS 中的队列和执行

作者: uniapp | 来源:发表于2020-04-08 22:42 被阅读0次

    GCD 凭借其高性能和易用性,得到了众多开发人员的青睐。在工作中涉及到多线程时,采用的往往也是 GCD。正确使用的前提是理解任务队列这两个概念。所以,iOS 面试中考察基本知识时,往往会涉及到这方面内容。下面就这两个概念,详细介绍。

    1 任务

    任务是 CPU 进行执行的基本单元,在 iOS 中可以简单地理解为一个函数实现。在GCD中可以认为任务等价于 block。比如,下面的函数体 test 就是一个 task 。

    -(void)test {
        NSLog(@"task1");
    }
    
    2 队列

    队列是任务的集合,强调的是一种静态表示。GCD 中队列分为 2 种: 顺序队列和并发队列。

    2.1 顺序队列

    如其名,顺序队列里面的任务有先后关系,符合先进先出的基本原则,只有排序在前面的任务执行完成,后面的任务才可以执行。比如下面的两个任务 task1task2 在主线程顺序执行,输出结果如下:

    /*
     task1
     task2
     */
    - (void)test_queue_1 {
        dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_SERIAL);
        dispatch_sync(s1, ^{
            NSLog(@"task1");
        });
        dispatch_sync(s1, ^{
            NSLog(@"task2");
        });
    }
    
    2.2 并发队列

    并发队列里面的任务,没有先后关系,同时抛出,供线程进行调度。比如,下面在主线程,执行并发队列 c1 中任务 task1、task2。

    /*
    task1
    task2
    */
    - (void)test_queue_2 {
        dispatch_queue_t c1 = dispatch_queue_create("concurrentOne", DISPATCH_QUEUE_CONCURRENT);
        dispatch_sync(c1, ^{
            NSLog(@"task1");
        });
        dispatch_sync(c1, ^{
            NSLog(@"task2");
        });
    }
    
    3 执行

    执行是对任务的调度,强调动态。执行是通过线程调度来完成的。根据线程调度的方式不同,分为异步和同步。异步是先执行当前线程任务,新添加的任务延迟执行。同步是停止当前线程任务,立即执行新添加的任务。比如,下面在主线程异步执行主队列的任务 task2, 输出结果如下:

    /*
    task1
    task3
    task2
    */ 
    -(void)test_asyn_1 {
        NSLog(@"task1");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"task2");
        });
        NSLog(@"task3");
    }
    

    关于执行,有以下 3 个特点:

    3.1 主队列任务一定在主线程
    /*
     task1-<NSThread: 0x600002b72d40>{number = 1, name = main}
     task2-<NSThread: 0x600002b72d40>{number = 1, name = main}
     */
    -(void)test_asyn_2 {
        NSLog(@"task1-%@",[NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"task2-%@",[NSThread currentThread]);
        });
    }
    
    3.2 非主队列任务

    对顺序队列,同步时不开辟新线程;对顺序队列,异步时开辟新线程,如下所示:

    /*
     task1-<NSThread: 0x600003ff20c0>{number = 1, name = main}
     task2-<NSThread: 0x600003ff20c0>{number = 1, name = main}
     task3-<NSThread: 0x600003ff5200>{number = 3, name = (null)}
     */
    - (void)test_asyn_3 {
        dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_SERIAL);
        NSLog(@"task1-%@",[NSThread currentThread]);
        dispatch_sync(s1, ^{
            NSLog(@"task2-%@",[NSThread currentThread]);
        });
        dispatch_async(s1, ^{
            NSLog(@"task3-%@",[NSThread currentThread]);
        });
    }
    

    对并发队列,同步时不开辟新线程;对并发队列,异步时开辟新线程,如下所示:

    /*
     task1-<NSThread: 0x60000068ed00>{number = 1, name = main}
     task2-<NSThread: 0x6000006e6240>{number = 6, name = (null)}
     task3-<NSThread: 0x6000006e6240>{number = 6, name = (null)}
     */
    - (void)test_asyn_4 {
        dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_CONCURRENT);
        NSLog(@"task1-%@",[NSThread currentThread]);
        dispatch_async(s1, ^{
            NSLog(@"task2-%@",[NSThread currentThread]);
            dispatch_sync(s1, ^{
                NSLog(@"task3-%@",[NSThread currentThread]);
            });
        });
    }
    

    由上:非主队列,是否开辟新线程要看执行者:也就是同步还是异步。同步时,不会开辟新线程;异步时,为了方便任务的及时执行,会开辟新线程;

    4 练习

    明白了以上概念,下面做两个小练习:

    4.1 练习一
    -(void)test3_1 {
        NSLog(@"task1-%@",[NSThread currentThread]);
        dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_SERIAL);
        dispatch_sync(s1, ^{
            NSLog(@"task2-%@",[NSThread currentThread]);
        });
        NSLog(@"task3-%@",[NSThread currentThread]);
        dispatch_async(s1, ^{
            NSLog(@"task4-%@",[NSThread currentThread]);
        });
        NSLog(@"task5-%@",[NSThread currentThread]);
    }
    

    请从打印顺序和是否新开线程,两个角度去思考。思考后,请看
    答案:

    /*
     task1-<NSThread: 0x600001c42140>{number = 1, name = main}
     task2-<NSThread: 0x600001c42140>{number = 1, name = main}
     task3-<NSThread: 0x600001c42140>{number = 1, name = main}
     task5-<NSThread: 0x600001c42140>{number = 1, name = main}
     task4-<NSThread: 0x600001c2e440>{number = 5, name = (null)}
     */
    
    4.1 练习二
    -(void)test3_2 {
        NSLog(@"task1-%@",[NSThread currentThread]);
        dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_CONCURRENT);
        dispatch_sync(s1, ^{
            NSLog(@"task2-%@",[NSThread currentThread]);
        });
        NSLog(@"task3-%@",[NSThread currentThread]);
        dispatch_async(s1, ^{
            NSLog(@"task4-%@",[NSThread currentThread]);
        });
        NSLog(@"task5-%@",[NSThread currentThread]);
    }
    

    答案二:

    /*
     task1-<NSThread: 0x600001248c00>{number = 1, name = main}
     task2-<NSThread: 0x600001248c00>{number = 1, name = main}
     task3-<NSThread: 0x600001248c00>{number = 1, name = main}
     task5-<NSThread: 0x600001248c00>{number = 1, name = main}
     task4-<NSThread: 0x600001217d00>{number = 3, name = (null)}
     */
    
    4.1 练习三

    将 4.2 中的同步队列,改成并发队列,会有什么结果呢?

    -(void)test3_3 {
        NSLog(@"task1-%@",[NSThread currentThread]);
        dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_CONCURRENT);
        dispatch_sync(s1, ^{
            NSLog(@"task2-%@",[NSThread currentThread]);
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"task3-%@",[NSThread currentThread]);
            });
        });
        NSLog(@"task4-%@",[NSThread currentThread]);
        dispatch_async(s1, ^{
            NSLog(@"task5-%@",[NSThread currentThread]);
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"task6-%@",[NSThread currentThread]);
            });
        });
        NSLog(@"task7-%@",[NSThread currentThread]);
    }
    

    答案三:

    /*
     task1-<NSThread: 0x600000145fc0>{number = 1, name = main}
     task2-<NSThread: 0x600000145fc0>{number = 1, name = main}
     task4-<NSThread: 0x600000145fc0>{number = 1, name = main}
     task7-<NSThread: 0x600000145fc0>{number = 1, name = main}
     task5-<NSThread: 0x60000012ea80>{number = 7, name = (null)}
     task3-<NSThread: 0x600000145fc0>{number = 1, name = main}
     task6-<NSThread: 0x600000145fc0>{number = 1, name = main}
     */
    
    4.1 练习四

    如果充分理解了以上 3 题,最后一个的题应该就很轻松了:

    -(void)test3_4 {
        NSLog(@"task1-%@",[NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"task2-%@",[NSThread currentThread]);
        });
        NSLog(@"task3-%@",[NSThread currentThread]);
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"task4-%@",[NSThread currentThread]);
        });
        NSLog(@"task5-%@",[NSThread currentThread]);
    }
    

    答案四:

    /*
     task1-<NSThread: 0x6000027f6d80>{number = 1, name = main}
     task3-<NSThread: 0x6000027f6d80>{number = 1, name = main}
     task5-<NSThread: 0x6000027f6d80>{number = 1, name = main}
     task2-<NSThread: 0x6000027f6d80>{number = 1, name = main}
     task4-<NSThread: 0x6000027f6d80>{number = 1, name = main}
     */
    

    以上是面试过程中遇到的基本知识题,口头表述一般都没问题。遇到像这种做题式的提问,还是需要对其执行有准确的认识,才可以快速得到结果。(完)

    喜欢和点赞都是对我的支持和鼓励~

    相关文章

      网友评论

        本文标题:深入理解 iOS 中的队列和执行

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