iOS 开发之 GCD解析(block 如何被添加进 queue 中,以及 block 执行)

GCD 是系统为我们提供的一套 c 语言的 API,可以用来进行多线程编程,下面一次来讲解一下 GCD 的相关 API
首先先来理解 几个概念


串行和并发;同步与异步


1. 串行和并发

串行:一次只能有一个任务执行
并发:在某一时间间隔内,有两个或者两个以上的任务一起执行(本质还是串行的,只不过按照时间片轮转的方式交替执行)。

2. 同步和异步

  1. 同步:同步就是任务要等到前面的任务执行完成之后他才可以执行,不会新开辟线程,所以对于一些耗时操作要放到子线程,不然会阻塞主线程
  2. 异步:不用等待前面的执行完,会新开辟一条线程去执行该任务。

GCD 的 相关概念


GCD 是系统为我们提供的一套 c 语言的 API,不是 Objective-c的对象,是对 NSThread 的封装。

1. Serial Dispatch Queue 和Concunrrent Dispatch Queue

*. Serial dispach Queue:串行队列
  1. 一次只能有一个任务运行
  2. 可以生成很多个,创建了多少个串行队列就生成了多少个线程,但是对于系统来说大量的创建串行队列,会浪费内存,引起大量的上下文切换,影响程序的响应性能
*. Concunrrent Dispatch Queue:并发队列
  1. 某一时间间隔里可以有多个任务同时运行
  2. 并发队列中并发任务的数量是由 XNU 内核的状态决定的
  3. 相对于串行队列,不管生成了多少个Concunrrent Dispatch Queue:,系统指挥管理有效的并发队列
  4. 当创建了多个并发队列的时候,各个队列之间是并发的。

2. Main Dispatch Queue 和Global Dispatch Queue

这是系统为我们默认提供的

*. Main Dispatch Queue:主队列
  1. 当程序开始运行时,系统会为我们创建一个主线程,诸葛主队列就在主线程里面,因为主线程只有一个,所以主队列也就只有一个,因此主队列是一个串行队列
  2. 被加入到主队列的任务,必须在主 runloop 中执行(runloop 与线程的关系请点这里runloop 与线程的关系)
  3. 主队列是由系统默认创建的,可以通过dispatch_get_main_queue()的方式来获取
  4. 对于界面的刷新是要放到主队列的.

*. Global Dispatch Queue:全局队列

  1. 一个全局队列,所有的应用程序都可以用,是并发的
  2. Global Dispatch Queue:有四个优先级
    *. 高优先级
    *. 低优先级
    *. 默认优先级
    *. 后台优先级
    由 XNU 内核管理的用于Global Dispatch Queue的的线程会将Global Dispatch Queue的的优先级作为线程的优先级。

GCD的 API


1. dispatch_queue_create(”queue名”,queue 属性)

  1. 第一个参数是队列名,第二个参数是队列的属性,返回值是 dispatch_queue_t 类型的
  2. 当第二个参数是 NULL,创建的是串行队列
 dispatch_queue_t serialQueue =  dispatch_queue_create("serial dispatch queue",  NULL);
    NSLog(@"%@",serialQueue);
    //打印结果:

当第二个参数是DISPATCH_QUEUE_CONCURRENT,创建的是并发队列

 dispatch_queue_t concurrentQueue =  dispatch_queue_create("concurrent dispatch queue",  DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"%@",concurrentQueue);
   //打印结果:

2. dispatch_async:创建异步任务(指定的 queue,要执行的 block 块)

  1. 第一个参数是队列(串行还是并发),第二个是我们要执行的任务块
  2. 会开启一个新的线程,来执行 block 中的内容,要是刷新界面需要切换到主队列
  3. 代码如下
        NSLog(@"开启一个异步任务%@",[NSThread currentThread]);
    });

2. dispatch_sync(指定的 queue,要执行的 block 块)

  1. 创建同步任务,不会开启新的线程,所以会阻塞主线程
  2. 会造成死锁(在回调的queue是main queue的前提下)
  3. 代码如下
dispatch_sync(Queue, ^{
        NSLog(@"开启一个同步任务");
    });

3. dispatch_set_target_queue:(Queue1,Queue2)改变队列的优先级

  1. 修改队列的优先级
    dispatch_queue_t test1 = dispatch_queue_create("test1",NULL);    dispatch_queue_t test2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);   
     dispatch_set_target_queue(test1, test2);
//第一个是要是修改优先级的 queue,第二个是参照物,将 test1的优先级设置为和 test2一样

应用1:不管是并行队列还是串行队列,当任务以异步的方式被添加到队列中,队列之间是并发的,要想让队列之间同步,可以将要同步的队列的目标队列设置为同一个,如下例子

    dispatch_queue_t test1 =  dispatch_queue_create("test1 dispatch queue", NULL );
    dispatch_queue_t test2 =  dispatch_queue_create("test2 dispatch queue1", NULL);
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我在test1 %d",i);
        }
    });
    dispatch_async(test2, ^{
        for (int i = 0; i<5; i++) {
        NSLog(@"我在test2 %d",i);
        }
    });   
//运行结果如下
2017-02-14 10:48:56.371 AFNetWorkingTestDemo[23510:1106181] 我在test1 0
2017-02-14 10:48:56.371 AFNetWorkingTestDemo[23510:1106181] 我在test1 1
2017-02-14 10:48:56.371 AFNetWorkingTestDemo[23510:1106174] 我在test2 0
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106181] 我在test1 2
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106181] 我在test1 3
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106181] 我在test1 4
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106174] 我在test2 1
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106174] 我在test2 2
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106174] 我在test2 3
2017-02-14 10:48:56.373 AFNetWorkingTestDemo[23510:1106174] 我在test2 4

可以看出两个队列交替打印,所以说队列之间是并发的
修改代码如下

    dispatch_queue_t test1 =  dispatch_queue_create("test1 dispatch queue", NULL );
    dispatch_queue_t test2 =  dispatch_queue_create("test2 dispatch queue1", NULL);
    dispatch_set_target_queue(test2, test1);//加了 该行代码
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我在test1 %d",i);
        }
    });
    dispatch_async(test2, ^{
        for (int i = 0; i<5; i++) {
        NSLog(@"我在test2 %d",i);
        }
    });
 //打印结果如下
2017-02-14 10:48:03.311 AFNetWorkingTestDemo[23484:1105222] 我在test1 0
2017-02-14 10:48:03.312 AFNetWorkingTestDemo[23484:1105222] 我在test1 1
2017-02-14 10:48:03.312 AFNetWorkingTestDemo[23484:1105222] 我在test1 2
2017-02-14 10:48:03.313 AFNetWorkingTestDemo[23484:1105222] 我在test1 3
2017-02-14 10:48:03.313 AFNetWorkingTestDemo[23484:1105222] 我在test1 4
2017-02-14 10:48:03.313 AFNetWorkingTestDemo[23484:1105222] 我在test2 0
2017-02-14 10:48:03.314 AFNetWorkingTestDemo[23484:1105222] 我在test2 1
2017-02-14 10:48:03.314 AFNetWorkingTestDemo[23484:1105222] 我在test2 2
2017-02-14 10:48:03.314 AFNetWorkingTestDemo[23484:1105222] 我在test2 3
2017-02-14 10:48:03.315 AFNetWorkingTestDemo[23484:1105222] 我在test2 4

可以看出test2在 test1完成以后 test2才开始执行

应用2:当一个任务被追加到不同的串行队列中,但是串行队列之间是并发的,可以通过设置优先级的方式让其同步执行

4. dispatch_barrier_async和dispatch_barrier_sync

1.比较
* 共同点:他之前的任务会在他之前完成,他之后的任务会等他在执行完后执行
* 不同点:dispatch_barrier_async后面的任务不会等他执行完再 被添加进 队列;dispatch_barrier_sync后面的任务会等他再执行完以后再添加 进队列
* 任务是 先添加进队列,但是并不是一添加进去就开始执行
2. 例子

//dispatch_barrier_async
 dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我是任务1");
        }
    });
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
        NSLog(@"我是任务2");
        }
    });
    NSLog(@"我在 dispatch_barrier_async之前");
    dispatch_barrier_async(test1, ^{//任务0
        for (int i = 0; i<5; i++) {
            NSLog(@"我是任务0");
        }
    });
    NSLog(@"我在 dispatch_barrier_async之后");//该行代码会在任务0还没有执行完就会被执行
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我是任务3");
        }
    });
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我是任务4");
        }
    });
    //dispatch_barrier_async打印结果如下
    2017-02-14 11:15:32.201 AFNetWorkingTestDemo[23777:1118873] 我是任务1
2017-02-14 11:15:32.201 AFNetWorkingTestDemo[23777:1118830] 我在 dispatch_barrier_async之前
2017-02-14 11:15:32.201 AFNetWorkingTestDemo[23777:1118866] 我是任务2
2017-02-14 11:15:32.202 AFNetWorkingTestDemo[23777:1118873] 我是任务1
2017-02-14 11:15:32.202 AFNetWorkingTestDemo[23777:1118830] 我在 dispatch_barrier_async之后
2017-02-14 11:15:32.202 AFNetWorkingTestDemo[23777:1118866] 我是任务2
2017-02-14 11:15:32.202 AFNetWorkingTestDemo[23777:1118873] 我是任务1
2017-02-14 11:15:32.203 AFNetWorkingTestDemo[23777:1118866] 我是任务2
2017-02-14 11:15:32.204 AFNetWorkingTestDemo[23777:1118873] 我是任务1
2017-02-14 11:15:32.204 AFNetWorkingTestDemo[23777:1118866] 我是任务2
2017-02-14 11:15:32.204 AFNetWorkingTestDemo[23777:1118873] 我是任务1
2017-02-14 11:15:32.205 AFNetWorkingTestDemo[23777:1118866] 我是任务2
2017-02-14 11:15:32.205 AFNetWorkingTestDemo[23777:1118866] 我是任务0
2017-02-14 11:15:32.206 AFNetWorkingTestDemo[23777:1118866] 我是任务0
2017-02-14 11:15:32.207 AFNetWorkingTestDemo[23777:1118866] 我是任务0
2017-02-14 11:15:32.208 AFNetWorkingTestDemo[23777:1118866] 我是任务0
2017-02-14 11:15:32.208 AFNetWorkingTestDemo[23777:1118866] 我是任务0
2017-02-14 11:15:32.208 AFNetWorkingTestDemo[23777:1118866] 我是任务3
2017-02-14 11:15:32.208 AFNetWorkingTestDemo[23777:1118873] 我是任务4
2017-02-14 11:15:32.225 AFNetWorkingTestDemo[23777:1118866] 我是任务3
2017-02-14 11:15:32.225 AFNetWorkingTestDemo[23777:1118873] 我是任务4
2017-02-14 11:15:32.247 AFNetWorkingTestDemo[23777:1118866] 我是任务3
2017-02-14 11:15:32.251 AFNetWorkingTestDemo[23777:1118873] 我是任务4
2017-02-14 11:15:32.251 AFNetWorkingTestDemo[23777:1118866] 我是任务3
2017-02-14 11:15:32.252 AFNetWorkingTestDemo[23777:1118873] 我是任务4
2017-02-14 11:15:32.252 AFNetWorkingTestDemo[23777:1118866] 我是任务3
2017-02-14 11:15:32.254 AFNetWorkingTestDemo[23777:1118873] 我是任务4
//dispatch_barrier_sync
//**如果把代码中的dispatch_barrier_async变成dispatch_barrier_sync
打印结果如下**
2017-02-14 11:29:22.922 AFNetWorkingTestDemo[23893:1123891] 我是任务1
2017-02-14 11:29:22.922 AFNetWorkingTestDemo[23893:1123881] 我是任务2
2017-02-14 11:29:22.922 AFNetWorkingTestDemo[23893:1123891] 我是任务1
2017-02-14 11:29:22.923 AFNetWorkingTestDemo[23893:1123891] 我是任务1
2017-02-14 11:29:22.923 AFNetWorkingTestDemo[23893:1123891] 我是任务1
2017-02-14 11:29:22.923 AFNetWorkingTestDemo[23893:1123891] 我是任务1
2017-02-14 11:29:22.923 AFNetWorkingTestDemo[23893:1123881] 我是任务2
2017-02-14 11:29:22.922 AFNetWorkingTestDemo[23893:1123797] 我在 dispatch_barrier_sync之前
2017-02-14 11:29:22.924 AFNetWorkingTestDemo[23893:1123881] 我是任务2
2017-02-14 11:29:22.925 AFNetWorkingTestDemo[23893:1123881] 我是任务2
2017-02-14 11:29:22.925 AFNetWorkingTestDemo[23893:1123881] 我是任务2
2017-02-14 11:29:22.926 AFNetWorkingTestDemo[23893:1123797] 我是任务0
2017-02-14 11:29:22.926 AFNetWorkingTestDemo[23893:1123797] 我是任务0
2017-02-14 11:29:22.927 AFNetWorkingTestDemo[23893:1123797] 我是任务0
2017-02-14 11:29:22.927 AFNetWorkingTestDemo[23893:1123797] 我是任务0
2017-02-14 11:29:22.928 AFNetWorkingTestDemo[23893:1123797] 我是任务0
2017-02-14 11:29:22.928 AFNetWorkingTestDemo[23893:1123797] 我在 dispatch_barrier_sync之后//会在任务0执行完再打印
2017-02-14 11:29:22.929 AFNetWorkingTestDemo[23893:1123891] 我是任务4
2017-02-14 11:29:22.929 AFNetWorkingTestDemo[23893:1123891] 我是任务4
2017-02-14 11:29:22.929 AFNetWorkingTestDemo[23893:1123881] 我是任务3
2017-02-14 11:29:22.930 AFNetWorkingTestDemo[23893:1123891] 我是任务4
2017-02-14 11:29:22.930 AFNetWorkingTestDemo[23893:1123891] 我是任务4
2017-02-14 11:29:22.930 AFNetWorkingTestDemo[23893:1123891] 我是任务4
2017-02-14 11:29:22.930 AFNetWorkingTestDemo[23893:1123881] 我是任务3
2017-02-14 11:29:22.931 AFNetWorkingTestDemo[23893:1123881] 我是任务3
2017-02-14 11:29:22.932 AFNetWorkingTestDemo[23893:1123881] 我是任务3
2017-02-14 11:29:22.932 AFNetWorkingTestDemo[23893:1123881] 我是任务3

从上面的例子中可以看出这两个的区别:其实不难理解,因为 async 是一部的,所以不会阻塞线程,但是 sync 会阻塞线程,所以同步和异步的区别是在这里体现的

5. dispatch_group_async(group ,queue,block)

  1. 会等添加进 group 中的所有任务执行完再执行 dispatch_group_notify的任务,如果我们想要任务3在任务1和2执行完后再执行,就把1和2放到同一个组里,例子如下
    dispatch_group_t group =  dispatch_group_create();//创建一个组
        //将任务1加入 group 中
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务1-1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"任务1-2");
    });
    //将任务2加入 group 中
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务2-1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"任务2-2");
    });
 dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任务3-1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"任务3-2");
    });
    //结果如下
    2017-02-14 11:44:49.912 AFNetWorkingTestDemo[24094:1132147] 任务1-1
2017-02-14 11:44:49.912 AFNetWorkingTestDemo[24094:1132138] 任务2-1
2017-02-14 11:44:52.912 AFNetWorkingTestDemo[24094:1132147] 任务1-2
2017-02-14 11:44:52.912 AFNetWorkingTestDemo[24094:1132138] 任务2-2
2017-02-14 11:44:52.914 AFNetWorkingTestDemo[24094:1132138] 任务3-1//任务3等到任务1和2执行完再执行
2017-02-14 11:44:55.916 AFNetWorkingTestDemo[24094:1132138] 任务3-2
  1. dispatch_group_async和dispatch_barrier_async 对比

    • 都会在其前面的任务完成之后再执行
    • 但是dispatch_barrier_async得后面的任务是在他执行完后再执行
      dispatch_group_async中没有加入组中的任务是在所有组中的任务完成以后再执行
  2. dispatch_group_wait和 dispatch_group_noticy

    • dispatch_group_wait可以指定在 group 的任务再执行了几秒时候做某些反应


    //第一个参数,开始时间、第二个参数当 group 中的任务执行多少面开始做某些事情,要是设置为DISPATCH_TIME_FOREVER,则会等 group 中的处理完再做,返回值为0,说明处理完,不为0,说明没处理完
    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW,DISPATCH_TIME_FOREVER);
    long result = dispatch_group_wait(group, timeout);
    >

  3. dispatch_group_noticy在 group 中的处理完再做

6:dispatch_semaphore_t:信号量机制

  1. 要进行操作前会判断semaphore的计数是不是大于等于1,是的话先让信号量计数-1,然后执行操作,操作执行完成后,再让信号量+1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);//设置信号量初始值为1
    NSMutableArray *array = [NSMutableArray array];
    for (int i = 0; i<5; i++) {
    //之要信号量值不大于等于1,就会一直等待,知道>=1,再对数组进行操作.
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [array addObject:[NSString stringWithFormat:@"%d",i]];
        //对数组进行完操作,让信号量计数+1,这样下次有线程要访问,就可以访问
        dispatch_semaphore_signal(semaphore);
    }

相当于操作系统中的 wait-sigal 操作,那个数组就是一个共享资源,只要有人在用(当 信号量计数为1),别的就会陷入等待,直到别人不用了,再去用,用之前先让计数-1,这样当别的人来用,就会知道有人在用,我还是等待吧,用完后,要记得让计数+1,释放这个资源,告诉别人这个资源我已经不用了,你可以用了**因为####7. dispatch_apply:让代码重复执行

//第一个参数是重复执行的次数,
//第二个参数是一个队列,
//第三个参数是下标,用来区分不同的 block
dispatch_apply(5, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"%d",index);
    });

应用:可以利用他来遍历数组

8. dispatch_once;


  1. 让代码在应用程序的生命周期中只执行一次
    可以用来实现单例,线程安全


static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
>

9. dispatch_suspend(Queue)和 dispatch_resume(Queue);

dispatch_suspend挂起一个尚未执行的 queue:如果这个 queue 已经在执行,没有影响,要是没有执行就停止执行
dispatch_resume让被挂起的 queue 继续执行。

10. dispatch_after()

延迟一段时间执行
不是一定时间后执行相应的任务,而是在一定时间后将其加入到队列中,队列中再分配相应的执行时间,主线程 runloop 1/60s检测时间,追加的范围为3s-(3+1/60)秒

总结

  1. dispatch_queue_create(“queue 名字”,queue的属性);
    • 第二个参数为 NULL:创建的是串行队列
    • 第二个参数为:DISPATCH_QUEUE_CONCURRENT,是并发队列
  2. dispatch_async:创建异步任务,会开启新线程,不会阻塞主线程
  3. dispatch_sync:创建同步任务,不会开启新线程,会阻塞主线程、造成 死锁
  4. dispatch_set_target_queue(“要修改优先级的队列”,“参照物”):修改队列的优先级/通过将多个队列设置到同一个目标队列上,可以让这些队列同步运行
  5. dispatch_barrier_async()和dispatch_barrier_async()都会等他们前面的任务执行完再执行,他们后面的任务会在他们执行完再执行,区别在于 async 后面的任务不会等到其执行完再加入 queue,但是 sync 会
  6. dispatch_group_async:会等组里面的任务都执行完了,再去执行dispatch_group_noticy中的任务
  7. dispatch_semaphore_t:信号量机制,保证线程操作安全,确保一次只有一个在操作共享资源,在操作前调用dispatch_semaphore_wait 申请资源,让计数-1,操作完要调用dispatch_semaphore_signal()释放资源,计数+1
  8. dispatch_once:让block 中代码在应用程序的生命周期里面只执行一次
  9. dispatch_suspend:挂起为执行的 queue,对于已经开始执行的 queue 没有作用
  10. dispatch_resume:让被挂起的 queue 恢复执行

Block 如何被添加进 Dispatch queue 中


block 并不是直接被添加进 Queue 中的,而是会先被添加进一个 Dispatch Continuation 的结构体中,这个结构体用于保存 block 所属的 group 以及其它一些信息。


block在 Queue 中如何执行


当 Global Diapatch Queue 开始执行 block 时,
1. libdispatch 会先从 Global Diapatch的自身的 FIFO 队列中取出 dispatch Continuation,
2. 然后调用 pthread_workqueue_additem_np 函数,将该 Global Disapatch 自身以及符合其优先级的workqueue 还有 dispatch Conitunation 的回调函数作为参数传递过去
3. pthread_workqueue_additem_np 使用 wokq_kernreturn 系统调用,通知 workqueue 增加应当执行的项目
4. XNU 内核根据该通知确定要不要生成线程,如果是 Overcommite 优先级的,则始终生成线程(优先级中有Overcommite使用在串行队列中,所以会始终生成线程)
5. workqueue 的线程执行 pthread_workqueue 函数,该函数调用libdispatch的回调函数,在该回调函数中执行加入到dispatch Continuation,的 block
6. block 执行结束后,进行通知 dispatch group 结束,释放dispatch Continuation, 准备下一个 block。

你可能感兴趣的:(iOS 开发之 GCD解析(block 如何被添加进 queue 中,以及 block 执行))