美文网首页
iOS-底层原理24:GCD 之 函数与队列

iOS-底层原理24:GCD 之 函数与队列

作者: AcmenL | 来源:发表于2021-01-04 17:24 被阅读0次

    在上一篇文章多线程中讲了些多线程基础知识,这篇文章以GCD为例进行深入分析。

    1 GCD简介

    GCD,全称Grand Central Dispatch(中央调度中心),纯C语言开发,提供了很多强大的函数

    1.1 GCD的优势

    • GCD是苹果公司为多核的并行运算提出的解决方案
    • GCD会自动利用更多的CPU内核(比如双核、四核)
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。

    程序员只需要告诉GCD想要执行的任务不需要编写任何线程管理相关代码(调度、销毁都不用管)

    【重点】: 将任务添加到队列,并指定执行任务的函数

    1.2 GCD核心

    这里用一个简单的实例,来帮助更好的理解任务+队列+执行任务的函数

    // 任务(block)
    dispatch_block_t block = ^{
        NSLog(@"hello GCD");
    };
    
    // 队列(此处串行队列)
    dispatch_queue_t queue = dispatch_queue_create("com.lbh.queue", DISPATCH_QUEUE_SERIAL);
    
    // 执行任务的函数(此处异步函数)
    dispatch_async(queue, block);
    
    • 使用dispatch_block_t创建任务
    • 使用dispatch_queue_t创建队列
    • 将任务添加到队列,并指定执行任务的函数dispatch_async

    任务是使用block封装函数,没有参数返回值。任务创建好后,等待执行任务的函数将其放入队列

    拓展:执行block,需要调用block(),这步调用,是执行任务的函数内部自动管理。
    后面解析dispatch源码时,可以清楚知道调用时机

    2 函数与队列

    2.1 函数

    在GCD中执行任务的方式有两种,同步执行异步执行,分别对应 同步函数dispatch_sync异步函数dispatch_async

    2.1.1 dispatch_sync 同步函数

    这个函数会把一个block加入到指定的队列中,而且会一直等到执行完blcok,这个函数才返回。因此在block执行完之前,调用dispatch_sync方法的线程是阻塞的

    • 必须等待当前语句执行完毕,才会执行下一条语句
    • 不会开启线程,即不具备开启新线程的能力
    2.1.2 dispatch_async 异步函数

    这个函数也会把一个block加入到指定的队列中,但是和同步执行不同的是,这个函数把block加入队列后不等block的执行就立刻返回了。

    • 不用等待当前语句执行完毕,就可以执行下一条语句
    • 开启线程执行block任务,即具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关)

    2.2 队列

    多线程中所说的队列(Dispatch Queue)是指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,遵循先进先出(FIFO)原则,即新任务总是被插入到队尾,而任务的读取从队首开始读取。

    GCD的队列包含串行队列并行队列两种

    2.2.1 串行队列

    每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个,即只开启一个线程(通俗理解:同一时刻只调度一个任务执行,类似单车道,汽车只能一辆辆排队通过)

    使用dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);创建串行队列,其中的DISPATCH_QUEUE_SERIAL也可以使用NULL表示

    // 串行队列的获取方法
    dispatch_queue_t serialQueue1 = dispatch_queue_create("com.lbh.Queue", NULL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("com.lbh.Queue", DISPATCH_QUEUE_SERIAL);
    
    2.2.2 并行队列

    一次可以并发执行多个任务,即开启多个线程,并同时执行任务(通俗理解:同一时刻可以调度多个任务执行,类似多车道,同时可以多辆汽车通过)

    使用dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);创建并发队列

    // 并发队列的获取方法
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lbh.Queue", DISPATCH_QUEUE_CONCURRENT);
    
    2.2.3 主队列

    主队列(Main Dispatch Queue),GCD中提供的特殊的串行队列

    • 专门用来在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前自动创建;

    • 不会开启线程

    • 如果当前主线程正在执行任务,需要等当前任务执行完,才会继续调度其他任务

    • 使用dispatch_get_main_queue()获得主队列

    //主队列的获取方法
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    2.2.4 全局并发队列

    全局并发队列(Global Dispatch Queue):GCD提供的默认的并发队列

    • 为了方便程序员的使用,苹果提供了全局队列

    • 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

    • 使用dispatch_get_global_queue获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)

      • 第一个参数表示队列优先级,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT=0,在ios9之后,已经被服务质量(quality-of-service)取代

      • 第二个参数使用0

    //全局并发队列的获取方法
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    //优先级从高到低(对应的服务质量)依次为
    - DISPATCH_QUEUE_PRIORITY_HIGH       -- QOS_CLASS_USER_INITIATED
    - DISPATCH_QUEUE_PRIORITY_DEFAULT    -- QOS_CLASS_DEFAULT
    - DISPATCH_QUEUE_PRIORITY_LOW        -- QOS_CLASS_UTILITY
    - DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND
    

    2.3 函数与队列的各种组合情况

    2.3.1 同步函数 + 串行队列
    /**
     串行同步队列 : FIFO: 先进先出
     */
    - (void)serialSyncTest{
        //1:创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_SERIAL);
        for (int i = 0; i<10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%d-%@",i,[NSThread currentThread]);
            });
        }
        NSLog(@"hello queue");
    }
    
    

    运行结果

    同步函数会执行完当前任务才继续执行下一个任务,没有开启新线程,是在主线程执行的

    分析

    问题:为什么block里的NSLog打印是在主线程?
    解答


    ⬇️

    dispatch_sync的官方注释里面有这么一句话:
    As an optimization, dispatch_sync() invokes the block on the current thread when possible.
    作为优化,如果可能,直接在当前线程调用这个block。

    所以,一般,在大多数情况下通过dispatch_sync添加的任务,在哪个线程添加就会在哪个线程执行,但在这个案例里 block仍是在自定义队列中执行,因为整个block放到主线程中会导致死锁。

    dispatch_queue_t queue = dispatch_queue_create("lbh", NULL);
        
    dispatch_async(queue, ^{
                 
        NSLog(@"current : %@", [NSThread currentThread]);
                 
        dispatch_queue_t serialQueue = dispatch_queue_create("lbh.1", DISPATCH_QUEUE_SERIAL);
         
        dispatch_sync(serialQueue, ^{
              // block 1
             NSLog(@"current 1: %@", [NSThread currentThread]);
        });
         
        dispatch_sync(serialQueue, ^{
            // block 2
            NSLog(@"current 2: %@", [NSThread currentThread]);
        });   
    });
    
    // 打印结果
    // current : <NSThread: 0x6000033c5d40>{number = 3, name = (null)}
    // current 1: <NSThread: 0x6000033c5d40>{number = 3, name = (null)}
    // current 2: <NSThread: 0x6000033c5d40>{number = 3, name = (null)}
    

    ⬆️


    2.3.2 同步函数 + 主队列
    - (void)mainSyncTest{
        
        NSLog(@"1-%@",[NSThread currentThread]);
    
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"-%@",[NSThread currentThread]);
        });
        
    }
    
    

    运行结果

    运行崩溃

    问题:为什么会崩溃?
    解答


    实际上是出现死锁

    ⬇️

    解释1:

    dispatch_sync同步函数添加任务,会在当前线程执行,而当前线程就是主线程dispatch_sync由于同步会造成线程阻塞,所以主线程会出现阻塞情况
    block任务是在主线程中执行的,而主线程又出现阻塞情况,所以block一直无法执行,block无法执行完成又会导致dispatch_sync同步函数一直执行。

    解释2:

    我们将上面的例子改一下:

    dispatch_queue_t queue = dispatch_queue_create("lbh", NULL);
        
    dispatch_async(queue, ^{
            
        NSLog(@"1-%@",[NSThread currentThread]);
            
        dispatch_sync(dispatch_get_main_queue(), ^{
             NSLog(@"-%@",[NSThread currentThread]);       
        }); 
    });
    
    // 打印结果
    // 1-<NSThread: 0x6000038ea880>{number = 5, name = (null)}
    // -<NSThread: 0x6000038ecfc0>{number = 1, name = main}
    

    此时dispatch_sync在子线程中,阻塞的是子线程,对主线程并没有影响

    ⬆️


    2.3.3 异步函数 + 串行队列
    /**
     串行异步队列
    
     */
    - (void)serialAsyncTest{
        //1:创建串行队列
        dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_SERIAL);
        for (int i = 0; i<10; i++) {
            
            dispatch_async(queue, ^{
                NSLog(@"%d-%@",i,[NSThread currentThread]);
            });
        }
        
        NSLog(@"hello queue");
    }
    
    

    运行结果

    分析

    问题:为什么NSLog(@"hello queue");执行在前?
    解答


    ⬇️

    这涉及到耗时问题,

    1 异步函数耗时

    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
        
    dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_SERIAL);
        
    dispatch_async(queue, ^{
        //        testMethod();
    });
    NSLog(@"= %f",CFAbsoluteTimeGetCurrent()-time);
    
    // 打印结果
    // = 0.000022
    

    2 同步函数耗时

    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
        
    dispatch_queue_t queue = dispatch_queue_create("com.lgcooci.cn", DISPATCH_QUEUE_SERIAL);
        
    dispatch_sync(queue, ^{
        //testMethod();
    });
    NSLog(@"= %f",CFAbsoluteTimeGetCurrent()-time);
    
    // 打印结果
    // = 0.000011
    

    3 主队列耗时

    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    
    NSLog(@"= %f",CFAbsoluteTimeGetCurrent()-time);
    // 打印结果
    // = 0.000000
    

    在主队列中几乎没有耗时,而同步和异步函数都有一定的耗时,所以NSLog(@"hello queue");执行在前

    ⬆️


    2.3.4 异步函数 + 主队列
    /**
     主队列异步
     不会开线程 顺序
     */
    - (void)mainAsyncTest{
        
        for (int i = 0; i<10; i++) {
            
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"%d-%@",i,[NSThread currentThread]);
            });
        }
        
        NSLog(@"hello queue");
    }
    

    运行结果

    异步函数不会等当前任务执行完就会执行下一个任务,没有开启新线程,是在主线程执行的

    分析

    2.3.5 同步函数 + 并行队列
    /**
     同步并发 : 堵塞 同步锁  队列 : resume supend   线程 操作, 队列挂起 任务能否执行
     */
    - (void)concurrentSyncTest{
        
        //1:创建并发队列
        dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i<10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%d-%@",i,[NSThread currentThread]);
            });
        }
        NSLog(@"hello queue");
    }
    

    运行结果

    分析

    2.3.6 异步函数 + 并发队列
    /**
     异步并发: 有了异步函数不一定开辟线程
     */
    - (void)concurrentAsyncTest{
        //1:创建并发队列
        dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
        for (int i = 0; i<10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%d-%@",i,[NSThread currentThread]);
            });
        }
        NSLog(@"hello queue");
    }
    

    运行结果

    乱序执行,开辟了新线程

    分析

    2.3.7 同步函数 + 全局并发队列
    /**
     全局同步
     全局队列:一个并发队列
     */
    - (void)globalSyncTest{
        
        for (int i = 0; i<10; i++) {
            dispatch_sync(dispatch_get_global_queue(0, 0), ^{
                NSLog(@"%d-%@",i,[NSThread currentThread]);
            });
        }
        NSLog(@"hello queue");
    }
    

    运行结果

    2.3.8 异步函数 + 全局并发队列
    /**
     全局异步
     全局队列:一个并发队列
     */
    - (void)globalAsyncTest{
        
        for (int i = 0; i<10; i++) {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                NSLog(@"%d-%@",i,[NSThread currentThread]);
            });
        }
        NSLog(@"hello queue");
    }
    

    运行结果

    总结
    函数\队列 串行队列 主队列 并发队列 全局并发队列
    同步函数 顺序执行,不开辟线程 死锁 顺序执行,不开辟线程 顺序执行,不开辟线程
    异步函数 顺序执行,开辟线程 顺序执行,不开辟线程 乱序执行,开辟线程 乱序执行,开辟线程

    3 面试题

    3.1 异步函数+并行队列

    - (void)interview01{
        
        dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
        
        NSLog(@"1- %@",[NSThread currentThread]);
        // 耗时
        dispatch_async(queue, ^{
            
            NSLog(@"2- %@",[NSThread currentThread]);
            
            dispatch_async(queue, ^{
                NSLog(@"3- %@",[NSThread currentThread]);
            });
            
            NSLog(@"4- %@",[NSThread currentThread]);
            
        });
        
        NSLog(@"5- %@",[NSThread currentThread]);
     
    }
    ----------打印结果-----------
    输出顺序为:1 5 2 4 3
    

    打印结果

    15 在主线程执行, 两个dispatch_async异步函数开辟了两个线程 24在第一个新线程执行 3在第二个新线程执行。

    分析

    我们可以以 自定义线程任务为分界 将程序拆成两部分进行分析:

    part1:

    dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
        
    NSLog(@"1- %@",[NSThread currentThread]);
    // 耗时
    dispatch_async(queue, ^{
                   
    });
        
    NSLog(@"5- %@",[NSThread currentThread]);
    

    part2:

    NSLog(@"2- %@",[NSThread currentThread]);
            
    dispatch_async(queue, ^{
        NSLog(@"3- %@",[NSThread currentThread]);
    });
            
    NSLog(@"4- %@",[NSThread currentThread]);
    

    综合两部分,最终打印顺序为: 1 5 2 4 3

    3.2 异步函数嵌套同步函数 + 并发队列

    dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
        
    NSLog(@"1- %@",[NSThread currentThread]);
        
    dispatch_async(queue, ^{
            
         NSLog(@"2- %@",[NSThread currentThread]);
            
         dispatch_sync(queue, ^{
             NSLog(@"3- %@",[NSThread currentThread]);   
         });
            
         NSLog(@"4- %@",[NSThread currentThread]);  
    });
    NSLog(@"5- %@",[NSThread currentThread]);
    
    ----------打印结果-----------
    输出顺序为:1 5 2 3 4
    

    运行结果

    1和5 在主线程执行, dispatch_async异步函数开辟了一个线程 ,由于dispatch_sync同步函数不会开辟新线程,所以234都是在这个线程中执行。

    分析

    part1 :同面试题1相同,这里不做讲解

    part2:

    NSLog(@"2- %@",[NSThread currentThread]);
            
    dispatch_sync(queue, ^{
         NSLog(@"3- %@",[NSThread currentThread]);   
    });
            
    NSLog(@"4- %@",[NSThread currentThread]); 
    

    综合两部分,最终打印顺序为: 1 5 2 3 4

    3.3 异步函数嵌套同步函数 + 串行队列

    - (void)interview03{
    // 同步队列
    dispatch_queue_t queue = dispatch_queue_create("lbh", NULL);
        
    NSLog(@"1- %@",[NSThread currentThread]);
    // 异步函数
    dispatch_async(queue, ^{
            
       NSLog(@"2- %@",[NSThread currentThread]);
       // 同步
       dispatch_sync(queue, ^{
            NSLog(@"3- %@",[NSThread currentThread]);
       });
            
      NSLog(@"4- %@",[NSThread currentThread]);   
    });
        
      NSLog(@"5- %@",[NSThread currentThread]);
            
    }
    

    运行结果

    打印152后出现死锁情况

    分析

    part1 :同面试题1相同,这里不做讲解

    part2

    NSLog(@"2- %@",[NSThread currentThread]);
            // 同步
    dispatch_sync(queue, ^{
           NSLog(@"3- %@",[NSThread currentThread]);
    });
            
    NSLog(@"4- %@",[NSThread currentThread]);
    

    这部分是在串行队列中执行

    综合两部分,最终打印顺序为: 1 5 2后出现死锁

    3.4 异步函数 + 同步函数 + 并发队列

    下面代码的执行顺序是什么?(答案是 AC)

    A: 1230789
    B: 1237890
    C: 3120798
    D: 2137890

    - (void)interview04{//
        
        dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
        // 1 2 3
        //  0 (7 8 9)
        dispatch_async(queue, ^{ // 耗时
            NSLog(@"1-%@",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"2-%@",[NSThread currentThread]);
        });
        
        // 堵塞哪一行
        dispatch_sync(queue, ^{
            NSLog(@"3-%@",[NSThread currentThread]);
        });
        
        NSLog(@"0-%@",[NSThread currentThread]);
        
        dispatch_async(queue, ^{
            NSLog(@"7-%@",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"8-%@",[NSThread currentThread]);
        });
        dispatch_async(queue, ^{
            NSLog(@"9-%@",[NSThread currentThread]);
        });
    }
    
    

    分析

    此题只能用排除法:打印3在打印0之前,全部满足;打印0在打印(7、8、9)之前,可以排除B 和 D ,答案为A、C

    4

    相关文章

      网友评论

          本文标题:iOS-底层原理24:GCD 之 函数与队列

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