美文网首页
iOS多线程之GCD

iOS多线程之GCD

作者: Arthurcsh | 来源:发表于2020-04-22 22:22 被阅读0次
    线程

    GCD是苹果公司为多核的并行运算解决方案,会自动利用更多的CPU内核,GCD自动管理线程的生命周期(创建、调度、销毁)

    GCD的两个核心概念
    • 任务:就是执行操作,具体的说是在线程中执行放在block中的那段代码,执行任务有两种方式:同步和异步,两者的区别:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

    同步执行(sync):

    1. 同步添加任务到指定队列中,中任务执行结束之前,会一直等待,直到队列里的任务完成后再执行。
    2. 只能在当前线程中执行任务,不具备开启新线程能力。

    异步执行(async):

    1. 异步添加任务到指定的队列中,不会做任务等待,可以继续执行任务。
    2. 可以在新的线程中执行任务,具备开启新线程能力。
    • 队列:用来存放任务的一种逻辑线性表,采用FIFO原则。GCD中的队列两种:串行队列和并发队列,主要区别:执行顺序不同,开启线程数不同。


      队列

    串行队列(Serial):


    串行队列

    每次只有一个任务被执行,只开启一个线程,一个任务执行完毕后,再执行下一个任务。

    并发队列(Concurrent):


    并发队列
    1. 可以让多个任务并发执行,可以开启多个线程,并且同时执行多任务。
    2. 注意:并发队列的并发功能只有在异步dispatch_async方法下才有效。
      主队列与全局队列:
    3. 主队列中的任务都在主线程中执行,主队列的特点:如果主队列发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务,直到主线程空闲为止。
    4. 全局队列本质上就是并发队列,与并发队列区别:并发队列有自定义名称,可以跟踪错误,全局队列没有,在ARC中不需要考虑内存释放,在MRC中需要手动dispatch_release(queue)释放内存,全局队列只有一个由系统管理。
    5. 一般情况下使用全局队列,但是对于同一类业务创建一个固定的队列进行管理,如SDK为两使被导入的SDK不影响到主应用的其他业务队列操作,建议创建自己专属的队列。
    GCD的使用步骤
    1. 创建一个队列(串行队列或并发队列);
    2. 将任务追加到指定的队列中,系统会根据任务类型执行任务(同步执行或异步执行)。
      使用dispatch_queue_create方法创建队列,两个参数:
    • 第一个参数表示队列唯一标识符,可为空,推荐使用应用模块ID逆序域名命名。
    • 第二个参数表示是串行队列还是并发队列:DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。
    • 对于串行队列,默认提供的是『主队列(Main Dispatch Queue)』。
    • 再强调一下:\color{red}{所有放在主队列的任务都会放到主线程中执行},使用dispatch_get_main_queue()获取主队列,主队列实质是个普通的 \color{red}{串行队列}
    • 对于并发队列, 默认提供了 『全局并发队列(Global Dispatch Queue)』,使用dispatch_get_global_queue方法获取全局并发队列,第一个参数表示队列优先级,一般用DISPATCH_QUEUE_RPIORITY_DEFAULT。第二个参数预置参数,用0即可。
    // 串行队列,异步执行:会新建一个线程,按串行顺序执行. DISPATCH_QUEUE_SERIAL = NULL
        dispatch_queue_t queue = dispatch_queue_create("com.chshua.dispatch", DISPATCH_QUEUE_SERIAL);
        for (int i=0; i<10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%@ --- %d", [NSThread currentThread], i);
            });
        }
    
    // 并行队列,异步执行:会新建多个线程,无法确定任务执行顺序
        dispatch_queue_t concurrent = dispatch_queue_create("com.chshua.dispatch", DISPATCH_QUEUE_CONCURRENT);
        for (int i=0; i<10; i++) {
            dispatch_async(concurrent, ^{
                NSLog(@"%@ --- %d", [NSThread currentThread], i);
            });
        }
    

    『主线程』中,『不同队列』+『不同任务』简单组合的区别:

    区别 并发队列 串行队列 主队列
    同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 死锁卡住不执行
    异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程, 串行执行任务

    注意: 『主线程』 中调用 『主队列』+『同步执行』 会导致死锁问题。
    这是因为 主队列中追加的同步任务 和 主线程本身的任务 两者之间相互等待,阻塞了 『主队列』,最终造成了主队列所在的线程(主线程)死锁问题。
    而如果我们在 『其他线程』 调用 『主队列』+『同步执行』,则不会阻塞 『主队列』,自然也不会造成死锁问题。最终的结果是:不会开启新线程,串行执行任务。

    主队列

    • 主队列,异步任务
    1. 不开线程,同步执行
    2. 主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后执行任务
    3. 主队列又叫全局串行队列
    • 主队列,同步任务
    1. 程序执行不出来(死锁)
    2. 死锁原因:主队列:如果主线程正在执行代码,就不调度任务。
      同步执行:如果第一个任务没有执行,就继续等待第一个任务执行完成,再执行下一个任务此时互相等待,程序无法往下执行(死锁)
    //主队列的特点:主队列只有当主线程空闲下来的时候才能够执行主队列里面的任务
    dispatch_sync(dispatch_get_main_queue(), ^{
           NSLog(@"哈哈, 大家都在等吧...");
    });
    

    一次执行dispatch_once
    作用是保证block在程序生命周期范围内只执行一次,最常见的场景是单例

    • 单例模式
    + (instancetype)sharedManager {
        static id instance;
        
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[self alloc] init];
        });
        return instance;
    }
    

    延迟执行dispatch_after

    • dispatch_after延迟执行GCD block方法,跟阻塞主线程有区别
    • dispatch_after能让添加队列的任务延时执行,该函数并不是在指定时间后执行处理,而只是在指定时间追加处理到dispatch_queue
    /**
    * dispatch_time 延迟时间
    * dispatch_get_main_queue 延迟执行的队列
    **/
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_queue_t queue, ^{
            // well to be executed after a specified delay
     });
    

    多次执行dispatch_apply

    • dispatch_apply把一项任务提交到队列中多次执行,具体是并行执行还是串行执行由队列本身决定.注意,dispatch_apply不会立刻返回,在执行完毕后才会返回,是同步的调用。
    /**
    * iterations  执行的次数  
    * queue       提交到的队列  
    * block       执行的任务
    **/
    dispatch_apply(size_t iterations, dispatch_queue_t queue,  ^{
          // code will to be executed
    });
    

    阻塞dispatch_barrier

    • dispatch_barrier_async用于等待前面的任务执行完毕后自己才执行,而它后面的任务需要等待它完成之后才执行。 应用场景读写锁操作,为了防止文件读写导致冲突,通常会创建一个串行队列,所有的文件操作都通过该队列来执行。
    • 在dispatch_barrier函数时,使用自定义队列才有意义,如果用串行队列或者系统提供的全局并发队列,这个栅栏函数的作用等同于一个同步函数的作用,dispatch_barrier_async 的作用会和 dispatch_async 的作用一模一样。


      dispatch_barrier
    • 有时需要异步执行两组操作,而且第一组操作执行完成后,才能开始执行第二组操作,这是需要一个相当于栅栏一样的方法将两组异步操作隔离起来。这里的操作组可以包含多个任务,用到dispatch_barrier_async方法在两个操作组之间形成栅栏。
    • \color{red}{dispatch\_barrier\_async} 方法会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在 dispatch_barrier_async 方法追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。
    /**
     * 栅栏方法 dispatch_barrier_async
     */
    - (void)barrier {
        dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
        
        dispatch_async(queue, ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queue, ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        dispatch_barrier_async(queue, ^{
            // 追加任务 barrier
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        });
        
        dispatch_async(queue, ^{
            // 追加任务 3
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        });
        dispatch_async(queue, ^{
            // 追加任务 4
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程
        });
    }
    

    队列组dispatch_group

    • 有这样一个需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
    • 调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。
    • 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
    /**
     * 队列组 dispatch_group_notify
     */
    - (void)groupNotify {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"group---begin");
        
        dispatch_group_t group =  dispatch_group_create();
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
            NSLog(@"group---end");
        });
    }
    
    • \color{red}{dispatch\_group\_wait}会暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
    /**
     * 队列组 dispatch_group_wait
     */
    - (void)groupWait {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"group---begin");
        
        dispatch_group_t group =  dispatch_group_create();
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 追加任务 1
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 追加任务 2
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        
        NSLog(@"group---end");
    }
    

    \color{red}{dispatch\_group\_wait} 相关代码运行输出结果可以看出:
    当所有任务执行完成之后,才执行 \color{red}{dispatch\_group\_wait} 之后的操作。但是,使用\color{red}{dispatch\_group\_wait} 会阻塞当前线程。

    相关文章

      网友评论

          本文标题:iOS多线程之GCD

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