iOS多线程:GCD

基本概念

  1. 什么是队列?

    • 队列是执行任务的等待队列,用来存放任务。
    • 它是一种特殊的线性表,采用FIFO(先进先出)的原则,新任务总是被插入到队列的末尾;
    • 读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
    • 队列负责管理多个任务,并拥有一个线程池,线程池中有一个或者多个线程。它按要求将每个任务调度到某个线程中执行。
  2. 串行队列 VS 并发队列

    队列类型决定了任务的执行方式(并发或者串行)

    串行队列:一个任务执行完毕后,继续执行下一个任务

    并发队列:多个任务同时执行

  3. 同步 VS 异步

    同步和异步决定是否了是否具备开启新线程的能力

    同步:在当前线程执行,不具备开启新线程的能力

    异步:在新的线程执行,具备开启新线程的能力

GCD

  1. GCD的特点

    • GCD是Apple开发的一个在线程池模式的基础上执行并发任务的多线程方案。
    • 它可以自动利用更多的CPU内核进行并行运算,并自动管理线程的生命周期;我们只需要追加要执行的任务到适当的队列中,不需要编写任何线程管理代码。
    • GCD的任务以block形式追加到队列中。
    • GCD默认提供了主队列,所有主队列的任务都会放到主线程执行
    • GCD默认提供了全局并发队列,使用dispatch_get_global_queue方法来获取。
  2. 同步任务(dispatch_sync)和异步任务(dispatch_async):

    dispatch_asyncdispatch_sync 的作用:

    • 任务(block)添加进指定的队列中
    • **async**和**sync决定调用该函数的线程是否需要阻塞,阻塞则等到block执行完毕后返回,不阻塞则立即返回**。
      • dispatch_sync(queue, block):把一个block加入到指定的队列,并阻塞当前线程直到执行完block,这个函数才返回;
      • dispatch_async(queue, block):把一个block加入到指定的队列中,不阻塞执行block的线程,这个函数立刻返回。
  3. 队列和任务的执行组合

    区别 并发队列 串行队列 主队列
    同步 没有开启新线程\br串行执行任务 没有开启新线程\br串行执行任务 没有开启新线程\br串行执行任务(死锁)
    异步 有开启新线程\br并发执行任务 有开启新线程\br串行执行任务 没有开启新线程\br串行执行任务
  4. 死锁

    使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列,产生死锁。

    示例1:

    1
    2
    3
    4
    5
    6
    7
    8
    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    dispatch_sync(dispatch_get_main_queue(), ^(void){
    NSLog(@"这里死锁了");
    });
    }
    return 0;
    }

    分析:

    在主线程上调用dispatch_sync,添加block到主队列,并阻塞当前线程也就是主线程等待block执行完成;

    主队列在主线程上调用,blockdispatch_asyn函数返回后才能执行,而主线程现在处于被阻塞状态,因为block执行后dispatch_sync函数才能返回。所以无法执行block。运行这段代码后,并不是卡在block中无法返回,而是根本无法执行block

    示例2:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    (void)deadLockCase2 { 
    dispatch_queue_t aSerialDispatchQueue = dispatch_queue_create(
    "com.test.deadlock.queue",
    DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_sync(aSerialDispatchQueue, ^{
    NSLog(@"2");
    dispatch_sync(aSerialDispatchQueue, ^{
    NSLog(@"3");
    });
    NSLog(@"4");
    });
    NSLog(@"5")
    }

    执行结果是1,2

    原因是外层dispatch_sync执行完打印4才能返回,然后打印后追加到队列的任务——打印3;内层dispatch_sync会阻塞当前的串行队列直到打印3之后才能返回去调用打印4,两者相互等待,造成死锁。

Dispatch Group

常用场景:分别异步执行两个任务,当两个任务都执行完毕后再回到主线程执行特定任务。

实现上分为两步:

  1. 添加耗时任务:dispatch_async + dispatch group
  2. 耗时任务完成后回到主线程执行特定任务:dispatch_group_notify / dispatch_group_wait / dispatch_group_enter + dispatch_group_leave
  • dispatch_group_notify示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    -(void)groupNotify {  
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t quque = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_group_async(group, queue, ^{
    // 异步任务1
    });
    dispatch_group_async(group, queue, ^{
    // 异步任务2
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 任务1、2结束后调用
    NSLog(@"group end");
    });
    }
  • dispatch_group_wait示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    -(void)groupWait { 
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(group, queue, ^{
    //追加异步任务1
    });
    dispatch_group_async(group, queue, ^{
    // 追加异步任务2
    });
    // 任务1、2执行完后,会往下继续执行
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group---end");
    }
  • dispatch_group_enter + dispatch_group_leave

    dispatch_group_enter:标志着一个任务追加到Group,执行一次,相当于Group中未执行任务数 + 1

    dispatch_group_leave:标志着一个任务离开了Group,执行一次,相当于Group中未执行任务数 - 1

    当Group中未执行任务数为0时,才会使dispatch_group_wait解除阻塞,并执行追加到dispatch_group_notify中的任务。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    -(void)groupEnterAndLeave { 
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
    // 追加任务1
    dispatch_group_leave(group); // 每执行完一个任务调用一次
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
    // 追加任务2
    dispatch_group_leave(group);
    });

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程.
    NSLog(@"group---end");
    });
    }

其他常用API

  1. 快速迭代方法:dispatch_apply

    dispatch_apply按照指定的次数将指定的任务追加到特定的队列中,并等待全部队列执行结束

    • 在串行队列:就和for循环一样,按顺序同步执行
    • 在并发队列:异步执行,可在多个线程中同时执行多个任务。

    无论是串行队列还是并发队列,dispatch_apply都会等待全部任务执行完毕。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 快速迭代
    -(void)apply{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    NSLog(@"apply begin");
    dispatch_apply(6, queue, ^(size_t index){
    NSLog(@"%zd---%@",index,[NSThread currentThread]);
    });
    NSLog(@"apply end");
    }
    // 因为在并发队列中异步执行任务,各个任务的执行时间长短不定,最后结束时间也不定。
    // apply end一定在最后执行,因为dispatch_apply方法会等待全部任务执行完毕。
  2. GCD信号量:dispatch_semaphore

    • semaphore的值表示线程最大并发数。
    • 常用来进行加锁操作;
    • 也可以用来控制线程的最大并发数。

    使用:

    • 信号量>0,使信号量-1,并继续执行;
    • 信号量<=0,新进来的线程就会进入休眠,直到dispatch_semaphore_signal(semaphore)使信号量>0时,被唤醒。

    dispatch_semaphore_create:创建一个Semaphore并初始化信号的总量

    dispatch_semaphore_wait:使信号量 -1,信号总量小于0就会一直阻塞所在线程,否则就可以正常执行。

    dispatch_semaphore_signal:发送一个信号,让信号总量加1

  3. dispatch_barrier_asyncdispatch_barrier_sync

    • 异步栅栏dispatch_barrier_async:不会等待栅栏函数内任务执行完,就会执行后面主线程的任务。

    • 同步栅栏dispatch_barrier_sync:会等待栅栏函数内的任务执行完,再执行后面的主线程或者子线程任务。

    优点:不会阻塞主线程

    生效必要条件:使用自定义的并发队列,不能使用全局并发队列。使用全局并发队列时,效果同dispatch_asyncdispatch_sync

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
    // 异步任务1
    });
    dispatch_async(queue, ^{
    // 异步任务2
    });
    // 任务1、2执行完成后执行栅栏任务
    dispatch_barrier_async(queue, ^{
    // 栅栏任务
    });
    dispatch_async(queue, ^{
    // 异步任务3
    });