美文网首页
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