Grand Central Dispatch(GCD)详解

概述


GCD是苹果异步执行任务技术,将应用程序中的线程管理的代码在系统级中实现。开发者只需要定义想要执行的任务并追加到适当的GCD中,Dispatch Queue就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样比以前的线程更有效率。


GCD的使用


dispatch_sync与dispatch_async

  • dispatch_sync
    synchronous同步,一旦调用dispatch_sync方法,那么指定的处理(block)追加到指定Dispatch Queue中在执行结束之前该函数都不会返回,也就是说当前的线程会阻塞,等待dispatch_sync在指定线程执行完成后才会继续向下执行。
    这里写图片描述

  • dispatch_async
    synchronous异步,一旦调用dispatch_async方法,那么指定的处理(block)追加到指定的Dispatch Queue中,dispatch_async不会做任何等待立刻返回,当前线程不受影响继续向下执行。
    这里写图片描述

注意
使用dispatch_sync容易造成死锁,一般情况下应该使用dispatch_async,例如

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
        NSLog(@"1");
    });
NSLog(@"2");   //这行代码不会输出

因为主线程等待dispatch_sync执行结束,而dispatch_sync又要在主线程中执行block。所以造成了死锁下面的代码不会执行。一般情况下应使用dispatch_async


  • Dispatch Queue
    Dispatch Queue是执行处理的等待队列,通过Block把想要执行的处理追加到Dispatch Queue中,根据追加的顺序通过FIFO(先进先出)来执行处理。

有两种类型的队列,一种是Serial Dispatch Queue(串行队列)等待正在执行中的处理当处理结束时再执行队列中下一个处理。一种是Concurrent Dispatch Queue(并发队列)不等待现在执行中的处理。

Serial Dispatch Queue ——-> 等待现在执行中的处理结束
Concurrent Dispatch Queue ——-> 不等待现在执行中的处理结束

用代码详细说明两种队列

dispatch_async(queue,block0);
dispatch_async(queue,block1);
dispatch_async(queue,block2);
dispatch_async(queue,block3);
dispatch_async(queue,block4);

queueSerial Dispatch Queue时输出

block0
block1
block2
block3
block4

queueConcurrent Dispatch Queue时输出

block1
block0
block2
block4
block3
  • Serial Dispatch Queue
    当上面的queueSerial Dispatch Queue时按顺序执行,先执行block0执行结束后执行block1block2依次类推,因为是串行执行所以系统此时只开辟了一个线程来处理。

    这里写图片描述

  • Concurrent Dispatch Queue
    当上面的queueConcurrent Dispatch Queue时,因为不用等待执行中的处理结束,所以首先执行block0,不管block0是否结束都开始执行后面的block1,不等block1执行结束都执行block2依次类推。以为是并发执行,实际上是开辟了多个线程同时执行多个处理。
    这里写图片描述


关于Concurrent Dispatch Queue线程问题
Concurrent Dispatch Queue中并行处理根据Dispatch Queue中的处理数量,机器CPU负载等一些状态来决定应该开辟多少个线程来处理。假如此时只能开辟三个线程但是要处理5个block事件系统应是下面这样处理:

线程0 block0,block3
线程1 block1,block4
线程2 block2

此时线程0处理block0,线程1处理block1,线程2处理block2。当block0执行玩后执行block3,可能此时block1还没有执行完,只有block1执行结束后才会执行block4。所以说并发队列执行的顺序是不确定的。


  • Main Dispatch Queue与Global Dispatch Queue

系统已经为我们提供了几种Dispatch Queue,不用我们去主动创建。Main Dispatch Queue把处理追加到当前的主线程RunLoop中执行。Global Dispatch Queue是把处理追加到一个Concurrent Dispatch Queue队列中处理。Global Dispatch Queue有四个优先级,系统提供的Dispatch Queue如下表所示:

Main Dispatch Queue —-> Serial Dispatch Queue —–> 主线程执行
Global Dispatch Queue (High Priority) —-> Concurrent Dispatch Queue —-> 执行优先级(高)
Global Dispatch Queue (Default Priority) —-> Concurrent Dispatch Queue —-> 执行优先级(默认)
Global Dispatch Queue (Low Priority) —-> Concurrent Dispatch Queue —-> 执行优先级(低)
Global Dispatch Queue (Background Priority) —-> Concurrent Dispatch Queue —-> 执行优先级(后台)

系统提供的Dispatch Queue获取

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globalQueueDefau = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globalQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

  • dispatch_set_target_queue
    自己通过dispatch_queue_creat函数创建的Dispatch Queue不管是串行队列还是并发的队列的优先级都是与Global Dispatch Queue的默认优先级相同,使用dispatch_set_target_queue可以变更Dispatch Queue的优先级 。
    dispatch_set_target_queue使用
dispatch_queue_t serialQueue = dispatch_queue_create("com.test.serialQueue", NULL);
dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_set_target_queue(serialQueue, globalQueueHigh);

上面的代码实现了通过dispatch_queue_creat创建的串行队列优先级默认变成了最高优先级,实现的效果是当有多个默认优先级的Serial Dispatch Queue并发执行时,如果设置了某一个Serial Dispatch Queue优先级为最高,那么先执行这个最高优先级的队列,然后再并发执行其他优先级相同的队列。


  • dispatch_after
    如果想在某一时间后执行某一操作,实现定时器的效果可以用dispatch_after来实现。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
    //从当前时间开始的10秒后
dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"");
    }); //将10秒后将要执行的操作追加到主线程进行执行

注意

上面的代码中dispatch_after并不是在10秒之后执行某一操作,而是在10秒后把要执行的操作追加到主线程中。比如主线程每0.01秒执一次RunLoop,那么这个追加操作最快10秒执行,最慢10+0.01秒执行。


  • Dispatch Group

上面介绍到如果使用Concurrent Dispatch Queue的话是不能确定队列中任务的执行顺序的,如果Concurrent Dispatch Queue中有三个任务要在这三个任务都执行结束后进行某个操作,这时就需要用到Dispatch Group

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
        NSLog(@"block0");
    });
dispatch_group_async(group, queue, ^{
        NSLog(@"block1");
    });
dispatch_group_async(group, queue, ^{
        NSLog(@"block2");
    });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"Finish");
    });

代码执行结果如下:

block2
block1
block0
Finish

当追加到Dispatch Group中的处理全部结束时,dispatch_group_notify将会执行追加的Block


  • dispatch_group_wait

dispatch_group_notify类似dispatch_group_wait也可以达到相同的效果

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"block0");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block2");
    });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"Finish");

执行结果如下:

block2
block1
block0
Finish

dispatch_group_wait的效果是等待group追加的操作全部执行完后再执行下面的代码,第二个参数表示等待的时间,DISPATCH_TIME_FOREVER表示一直等待group的处理结果。直到处理完成才执行下面的代码。

如果只想等待一段指定的时间的话改变DISPATCH_TIME_FOREVER即可

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    if (result == 0) {
         NSLog(@"Finish");
    }
    else{}

上面代码表示只等待1秒不管group中的处理是否全部完成都要执行下面的代码,当result = 0表示group中的处理已经处理完成,否则没有完成。


  • dispatch_barrier_async
    如果在Concurrent Dispatch Queue中追加五个操作,这时想先并发执行前三个操作,等前三个操作都执行结束后再并发的执行后两个操作,这时就需要用到dispatch_barrier_async函数,具体实现如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"block0");
    });
    dispatch_async(queue, ^{
        NSLog(@"block1");
    });
    dispatch_async(queue, ^{
        NSLog(@"block2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"barrier");
    });
    dispatch_async(queue, ^{
        NSLog(@"block3");
    });
    dispatch_async(queue, ^{
        NSLog(@"block4");
    });

代码执行结果如下:

block1
block0
block2
barrier
block3
block4

使用dispatch_barrier_async会等待在它之前追加到Concurrent Dispatch Queue中的所有操作都执行结束之后,再执行在它之后追加到Concurrent Dispatch Queue中的操作。

与Dispatch Group的区别

Dispatch Group是等待追加到它队列里面的所有操作执行结束。而dispatch_barrier_async是等待在它之前追加到它对列里面的操作。一个是等待队列执行结束,一个是等待队列中某些操作执行结束。


  • dispatch_apply

dispatch_apply是按照指定的次数把操作(block)追加到指定的Dispatch Group
示例1

dispatch_queue_t queueSerial = dispatch_queue_create("com.myProject.queueSerial", NULL);
dispatch_apply(5, queueSerial, ^(size_t index) {
        NSLog(@"%zu",index);
    });

运行结果

0
1
2
3
4

示例2

dispatch_queue_t queueCurrnt = dispatch_queue_create("com.myProject.queueCurrnt", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5, queueCurrnt, ^(size_t index) {
        NSLog(@"%zu",index);
    });

运行结果:

3
0
1
2
4

dispatch_apply第一个参数是要向队列中追加几次操作,第二个参数是将要追加操作的次数,第三个参数是用来区分第几次追加的操作。示例1中是向串行队列Serial Dispatch Queue追加操作。示例2中是向并发队列Concurrent Dispatch Queue追加操作。


  • dispatch_queue_create

在上面的示例中用到了dispatch_queue_create函数这个是用来创建Serial Dispatch QueueConcurrent Dispatch Queue。这个函数第一个参数是队列的标识符,标识符的写法最好按照域名倒写的方法来表示Dispatch Queue,这样方便在调试中查看。第二个参数表示创建队列的类型当为NULL表示创建串行队列,DISPATCH_QUEUE_CONCURRENT表示创建并行队列。


  • dispatch_suspend与dispatch_resume

如果再进行某个操作时,不想执行队列中的操作,在这个操作完成时再执行队列中的操作,这时用dispatch_suspend会挂起当前的队列,此时不会执行队列中追加的操作。而用dispatch_resume会恢复挂起的队列。dispatch_suspenddispatch_resume必须成对调用,有挂起就应该有恢复。

dispatch_queue_t queue = dispatch_queue_create("com.myProject.queueCurrnt", NULL);
    dispatch_suspend(queue);
    dispatch_async(queue, ^{
        for (unsigned int i = 0; i<10; i++) {
            NSLog(@"Concurrent");
        }
    });

    for (unsigned int i = 0; i<10; i++) {
        NSLog(@"Serial");
    }
    dispatch_resume(queue);

如果没有dispatch_suspenddispatch_resume那么”Concurrent“与”Serial“会交替的输出,如果使用dispatch_suspend会把队列挂起然后执行下面的代码当”Serial“全部输出之后dispatch_resume恢复队列开始输出”Concurrent“。


  • dispatch_once
    如果在应用中一段代码只想让它执行一次,那么就需要用到dispatch_once,一般用于创建单例。
static NSObject *object = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object=[[NSObject alloc] init];
    });

本片文章参考这儿.


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值