iOS进阶之多线程--GCD

GCD简介

  • GCD全称Grand Central Dispatch,可译为“牛逼的中枢调度系统”,是苹果公司为多核的并行运算提供的解决方案。
  • 开发者借助GCD无需直接操作线程,只需要将准备好的和要执行的任务添加到Dispatch Queue(队列)中,GCD会根据队列类型(串行&并发)和任务的执行类型(同步&异步)来确定要不要开启子线程、和任务的执行顺序。
  • 任务的执行顺序遵循队列的FIFO原则,先进先出、后进后出。
  • 并且要不要开启线程、开几条线程以及线程的生命周期(创建线程、调度热舞、销毁线程)都不需要开发者关心。
  • GCD会自动利用更多的CPU内核(比如:双核、四核、乃至八核),本人也相信苹果的GCD技术也是一大伏笔,虽然目前市场上的苹果手机大都是双核,相信在不久的未来苹果推出更多核芯CPU时GCD会自动适配,届时所有程序员会发现,苹果已经早先一步有了应对多核硬件的技术,并且也不用担心硬件的革新带来项目重构的问题,所以放心地使用GCD!

GCD的几个概念

  1. 任务 同步&异步:
 /**同步执行*/
dispatch_sync(dispatch_queue_t  _Nonnull queue, ^(void)block)
/**异步步执行*/
dispatch_async(dispatch_queue_t  _Nonnull queue, ^(void)block)

这里只要执行的代码,为Block代码块。既然是Block,是不是烦恼的循环引用又来了,block只是一个局部变量,执行完毕之后就释放掉了,不用担心循环引用问题。任务的执行又分同步和异步。
同步:当前任务(代码)没有执行完毕不会执行下一个任务;
异步:当前任务没有执行完毕同样会执行下一个任务(只要有任务,GCD就回到线程池中取线程)(主队列除外)

  1. 队列 串行&并发:
/**
参数:
1.线程名称
2.DISPATCH_QUEUE_SERIAL == NULL 串行
  DISPATCH_QUEUE_CONCURRENT  并发
*/
dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t  _Nullable attr)

主要负责调度任务,所有队列都遵循FIFO原则。队列又分为串行队列和并发队列。
串行 队列:一个一个地调度任务。
并发 队列:可以同时调度多个任务,开不开线程是有任务决定的。如果是同步任务:当一个任务没有执行完成,队列也会取任务,只是取出的任务要等待前一个任务执行完毕才开始执行,不会开启线程;如果是异步任务:GCD会开启线程同时执行多个任务。

  1. 小结:
    开不开线程取决于任务,同步不开线程,异步开线程;
    开几条线程由队列决定,串行只开一条,并发(只有异步条件下)可以开启多条。

GCD的相关用法

  1. 串行队列,同步任务
    //串行
    dispatch_queue_t q = dispatch_queue_create("chenjinguo", NULL);
    for (int i = 0; i < 10; i ++) {
        //同步
        dispatch_sync(q, ^{
            NSLog(@"%@   %d",[NSThread currentThread],i);
        });
    }

打印

2018-04-03 11:53:46.883277+0800 GCD演示[1099:402251] {number = 1, name = main}   0
2018-04-03 11:53:46.883576+0800 GCD演示[1099:402251] {number = 1, name = main}   1
2018-04-03 11:53:46.883763+0800 GCD演示[1099:402251] {number = 1, name = main}   2
2018-04-03 11:53:46.883928+0800 GCD演示[1099:402251] {number = 1, name = main}   3
2018-04-03 11:53:46.884098+0800 GCD演示[1099:402251] {number = 1, name = main}   4
2018-04-03 11:53:46.884260+0800 GCD演示[1099:402251] {number = 1, name = main}   5
2018-04-03 11:53:46.884390+0800 GCD演示[1099:402251] {number = 1, name = main}   6
2018-04-03 11:53:46.884529+0800 GCD演示[1099:402251] {number = 1, name = main}   7
2018-04-03 11:53:46.884648+0800 GCD演示[1099:402251] {number = 1, name = main}   8
2018-04-03 11:53:46.884789+0800 GCD演示[1099:402251] {number = 1, name = main}   9
  • 不会开启线程,顺序执行
  1. 串行队列,同步任务
  //串行
  dispatch_queue_t q = dispatch_queue_create("chen_jinguo",DISPATCH_QUEUE_CONCURRENT);
  for(int i = 0; i < 10; i ++){
     NSLog(@"---------%d-------",i);
    //异步
    dispatch_async(q, ^{
        NSLog(@"%@ %d",[NSThread currentThread],i);
    });
  }

打印

2018-04-03 11:58:17.697224+0800 GCD演示[1126:425688] ---------0-------
2018-04-03 11:58:17.697617+0800 GCD演示[1126:426370] {number = 3, name = (null)}   0
2018-04-03 11:58:17.697436+0800 GCD演示[1126:425688] ---------1-------
2018-04-03 11:58:17.698929+0800 GCD演示[1126:425688] ---------2-------
2018-04-03 11:58:17.699189+0800 GCD演示[1126:425688] ---------3-------
2018-04-03 11:58:17.699223+0800 GCD演示[1126:426370] {number = 3, name = (null)}   1
2018-04-03 11:58:17.699405+0800 GCD演示[1126:425688] ---------4-------
2018-04-03 11:58:17.699447+0800 GCD演示[1126:426370] {number = 3, name = (null)}   2
2018-04-03 11:58:17.701129+0800 GCD演示[1126:425688] ---------5-------
2018-04-03 11:58:17.702702+0800 GCD演示[1126:426370] {number = 3, name = (null)}   3
2018-04-03 11:58:17.703452+0800 GCD演示[1126:425688] ---------6-------
2018-04-03 11:58:17.703831+0800 GCD演示[1126:426370] {number = 3, name = (null)}   4
2018-04-03 11:58:17.704094+0800 GCD演示[1126:425688] ---------7-------
2018-04-03 11:58:17.704316+0800 GCD演示[1126:426370] {number = 3, name = (null)}   5
2018-04-03 11:58:17.743690+0800 GCD演示[1126:425688] ---------8-------
2018-04-03 11:58:17.743782+0800 GCD演示[1126:426370] {number = 3, name = (null)}   6
2018-04-03 11:58:17.743979+0800 GCD演示[1126:425688] ---------9-------
2018-04-03 11:58:17.744001+0800 GCD演示[1126:426370] {number = 3, name = (null)}   7
2018-04-03 11:58:17.744380+0800 GCD演示[1126:425688] come here
2018-04-03 11:58:17.744407+0800 GCD演示[1126:426370] {number = 3, name = (null)}   8
2018-04-03 11:58:17.744776+0800 GCD演示[1126:426370] {number = 3, name = (null)}   9
  • 会开启一条线程,顺序执行
  1. 并发队列,异步任务
    //队列 - 并发 DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t q = dispatch_queue_create("chenjinguo", DISPATCH_QUEUE_CONCURRENT);
    //执行 - 异步
    for (int i = 0; i < 10; i ++) {
        dispatch_async(q, ^{
            NSLog(@"%@   %d",[NSThread currentThread],i);
        });
    }
    NSLog(@"come here");

打印

2018-04-03 13:36:05.457846+0800 GCD演示[1213:499948] come here
2018-04-03 13:36:05.458007+0800 GCD演示[1213:500170] {number = 4, name = (null)}   1
2018-04-03 13:36:05.458008+0800 GCD演示[1213:500169] {number = 3, name = (null)}   0
2018-04-03 13:36:05.458010+0800 GCD演示[1213:500171] {number = 6, name = (null)}   3
2018-04-03 13:36:05.458012+0800 GCD演示[1213:500180] {number = 5, name = (null)}   2
2018-04-03 13:36:05.458301+0800 GCD演示[1213:500170] {number = 4, name = (null)}   5
2018-04-03 13:36:05.458310+0800 GCD演示[1213:500168] {number = 7, name = (null)}   4
2018-04-03 13:36:05.458323+0800 GCD演示[1213:500169] {number = 3, name = (null)}   6
2018-04-03 13:36:05.458345+0800 GCD演示[1213:500171] {number = 6, name = (null)}   7
2018-04-03 13:36:05.458451+0800 GCD演示[1213:500168] {number = 7, name = (null)}   8
2018-04-03 13:36:05.458476+0800 GCD演示[1213:500170] {number = 4, name = (null)}   9
  • 会开启多条线程,非顺序执行
  1. 并发队列,同步任务
    //队列 - 并发 DISPATCH_QUEUE_CONCURRENT
    dispatch_queue_t q = dispatch_queue_create("chenjinguo", DISPATCH_QUEUE_CONCURRENT);
    //执行 - 同步
    for (int i = 0; i < 10; i ++) {
        dispatch_sync(q, ^{
            NSLog(@"%@   %d",[NSThread currentThread],i);
        });
    }
    NSLog(@"come here");

打印

2018-04-03 13:37:58.162147+0800 GCD演示[1228:510822] {number = 1, name = main}   0
2018-04-03 13:37:58.162436+0800 GCD演示[1228:510822] {number = 1, name = main}   1
2018-04-03 13:37:58.163300+0800 GCD演示[1228:510822] {number = 1, name = main}   2
2018-04-03 13:37:58.163621+0800 GCD演示[1228:510822] {number = 1, name = main}   3
2018-04-03 13:37:58.163761+0800 GCD演示[1228:510822] {number = 1, name = main}   4
2018-04-03 13:37:58.163916+0800 GCD演示[1228:510822] {number = 1, name = main}   5
2018-04-03 13:37:58.164035+0800 GCD演示[1228:510822] {number = 1, name = main}   6
2018-04-03 13:37:58.164410+0800 GCD演示[1228:510822] {number = 1, name = main}   7
2018-04-03 13:37:58.164839+0800 GCD演示[1228:510822] {number = 1, name = main}   8
2018-04-03 13:37:58.165329+0800 GCD演示[1228:510822] {number = 1, name = main}   9
2018-04-03 13:37:58.165824+0800 GCD演示[1228:510822] come here
  • 不会开启线程,顺序执行
  1. 同步任务
    在开发中,通常会把耗时任务放在后台执行,有时候,有些任务彼此有“依赖”关系!
    例子:登录、支付、下载
    利用同步任务,能够任务的依赖关系,前一个是同步任务,如果不执行完,队列就不会调度后面的任务
dispatch_queue_t q = dispatch_queue_create("ChenJinguo", DISPATCH_QUEUE_CONCURRENT);
    //1、登录
    dispatch_sync(q, ^{
        NSLog(@"用户登录----- %@",[NSThread currentThread]);
        
    });
    //2、支付
    dispatch_sync(q, ^{
        NSLog(@"用户支付----- %@",[NSThread currentThread]);
    });
    for (int i = 0; i < 10; i ++) {
        NSLog(@"%d\n",i);
    }

    //3、下载
    dispatch_async(q, ^{
        NSLog(@"用户下载----- %@",[NSThread currentThread]);
    });
    NSLog(@"come here!");

打印

2018-04-03 13:43:05.538233+0800 GCD演示[1264:539460] 用户登录----- {number = 1, name = main}
2018-04-03 13:43:05.538480+0800 GCD演示[1264:539460] 用户支付----- {number = 1, name = main}
2018-04-03 13:43:05.538938+0800 GCD演示[1264:539460] 0
2018-04-03 13:43:05.539076+0800 GCD演示[1264:539460] 1
2018-04-03 13:43:05.539220+0800 GCD演示[1264:539460] 2
2018-04-03 13:43:05.539321+0800 GCD演示[1264:539460] 3
2018-04-03 13:43:05.539431+0800 GCD演示[1264:539460] 4
2018-04-03 13:43:05.539679+0800 GCD演示[1264:539460] 5
2018-04-03 13:43:05.540171+0800 GCD演示[1264:539460] 6
2018-04-03 13:43:05.540635+0800 GCD演示[1264:539460] 7
2018-04-03 13:43:05.541084+0800 GCD演示[1264:539460] 8
2018-04-03 13:43:05.541530+0800 GCD演示[1264:539460] 9
2018-04-03 13:43:05.541870+0800 GCD演示[1264:539460] come here!
2018-04-03 13:43:05.541957+0800 GCD演示[1264:540171] 用户下载----- {number = 3, name = (null)}
  • 因为登录和支付都是同步任务,在执行完同步任务之后才会开启子线程完成其他异步任务

思考:如果登录、支付和下载都是耗时任务,为了增强用户体验,不想将他们放在UI线程中,怎么样完成这三个任务的同步呢?
方法:将三个任务作为一个异步任务,这样不管放在串行队列或者并发队列中,GCD都会开辟子线程调度此任务,然后在开启的子线程上同步调度三个任务,就完成了异步中同步调度任务

  dispatch_queue_t q = dispatch_queue_create("chen", DISPATCH_QUEUE_CONCURRENT);
  //包装三个任务为block  异步执行
    void (^task)() = ^{
        dispatch_sync(q, ^{
            for (int i = 0; i < 10; i ++) {
                NSLog(@"%d--- %@\n",i,[NSThread currentThread]);
            }
        });

        //1、登录
        dispatch_async(q, ^{
            NSLog(@"用户登录----- %@",[NSThread currentThread]);
            
        });
        //2、支付
        dispatch_async(q, ^{
            NSLog(@"用户支付----- %@",[NSThread currentThread]);
        });

        
        //3、下载
        dispatch_async(q, ^{
            NSLog(@"用户下载----- %@",[NSThread currentThread]);
        });
    };
    dispatch_async(q, task);

打印

2018-04-03 13:54:32.628778+0800 GCD演示[1297:579094] 0--- {number = 4, name = (null)}
2018-04-03 13:54:32.628785+0800 GCD演示[1297:579087] 用户登录----- {number = 3, name = (null)}
2018-04-03 13:54:32.629000+0800 GCD演示[1297:579094] 1--- {number = 4, name = (null)}
2018-04-03 13:54:32.629150+0800 GCD演示[1297:579087] 用户支付----- {number = 3, name = (null)}
2018-04-03 13:54:32.629169+0800 GCD演示[1297:579094] 2--- {number = 4, name = (null)}
2018-04-03 13:54:32.629267+0800 GCD演示[1297:579094] 3--- {number = 4, name = (null)}
2018-04-03 13:54:32.629269+0800 GCD演示[1297:579087] 用户下载----- {number = 3, name = (null)}
2018-04-03 13:54:32.629835+0800 GCD演示[1297:579094] 4--- {number = 4, name = (null)}
2018-04-03 13:54:32.630147+0800 GCD演示[1297:579094] 5--- {number = 4, name = (null)}
2018-04-03 13:54:32.630346+0800 GCD演示[1297:579094] 6--- {number = 4, name = (null)}
2018-04-03 13:54:32.630643+0800 GCD演示[1297:579094] 7--- {number = 4, name = (null)}
2018-04-03 13:54:32.631249+0800 GCD演示[1297:579094] 8--- {number = 4, name = (null)}
2018-04-03 13:54:32.631599+0800 GCD演示[1297:579094] 9--- {number = 4, name = (null)}

这样所有的任务都会在子线程中执行,并且三个任务之间有依赖关系

  1. 全局队列
    GCD提供默认的并发队列供全局使用:全局队列。其获取方式和参数如下:
dispatch_get_global_queue(long identifier, unsigned long flags);
  参数类型为:
    long identifier:ios 8.0 告诉队列执行任务的“服务质量 quality of service”,系统提供的参数有:
     QOS_CLASS_USER_INTERACTIVE 0x21,              用户交互(希望尽快完成,用户对结果很期望,不要放太耗时操作)
     QOS_CLASS_USER_INITIATED 0x19,                用户期望(不要放太耗时操作)
     QOS_CLASS_DEFAULT 0x15,                        默认(不是给程序员使用的,用来重置对列使用的)
     QOS_CLASS_UTILITY 0x11,                        实用工具(耗时操作,可以使用这个选项)
     QOS_CLASS_BACKGROUND 0x09,                     后台
     QOS_CLASS_UNSPECIFIED 0x00,                    未指定
     iOS 7.0 之前 优先级
     DISPATCH_QUEUE_PRIORITY_HIGH 2                 高优先级
     DISPATCH_QUEUE_PRIORITY_DEFAULT 0              默认优先级
     DISPATCH_QUEUE_PRIORITY_LOW (-2)               低优先级
     DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后台优先级

    BACKGROUND表示用户不需要知道任务什么时候完成,如果选择这个选项速度慢得令人发指,非常不利于调试!对于优先级推荐不要搞得太负责,就用最简单,以免发生优先级反转。
     
    unsigned long flags:苹果官方文档是这样解释的: Flags that are reserved for future use。标记是为了未来使用保留的!所以这个参数应该永远指定为0

注意:全局队列属于并发队列,建议在企业级应用开发过程中如果不使用全局队列,尽量给队列起名,这样有利于错误跟踪;另外,在MRC模式下,队列需要releasedispatch_release(q);//ARC 情况下不需要release!

  1. 延时执行dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
    /**参数
     1、when 时间
     2、queue
     3、Block
     */
    //从现在开始执行多少纳秒之后,让queue调度Block的任务并且异步执行
    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC);
    dispatch_after(when, dispatch_queue_create("jinguo", NULL), ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
  1. 执行一次dispatch_once(dispatch_once_t * _Nonnull predicate, ^(void)block) ;
    执行一次经常在单例中用到,是苹果提供的一次性机制,不仅能保证代码只执行一次,并且线程安全,线程安全表现在:dispatch_once_t * _Nonnull predicate参数,其原理官方头文件也有表现:
_dispatch_once(dispatch_once_t *predicate,
        DISPATCH_NOESCAPE dispatch_block_t block)
{
    if (DISPATCH_EXPECT(*predicate, ~0l) != ~0l) {
        dispatch_once(predicate, block);
    } else {
        dispatch_compiler_barrier();
    }
    DISPATCH_COMPILER_CAN_ASSUME(*predicate == ~0l);
}

其实具体是什么原理我也看不懂,毕竟C语言认识我我不认识它,其大概意思是我们外面定义的dispatch_once_t参数在传入这个函数之后,使用它来保证线程安全的,其实我们做一次打印也可以看出个大概

   for (int i = 0; i < 10; i ++) {
       dispatch_async(dispatch_get_global_queue(0, 0), ^{
           static dispatch_once_t onceToken;
           NSLog(@"%ld",onceToken);
           dispatch_once(&onceToken, ^{
               NSLog(@" %@come here!",[NSThread currentThread]);
           });
           NSLog(@"来了 ----%d %@",i,[NSThread currentThread]);
       });
   }

打印

2018-04-03 14:17:22.679810+0800 GCD演示[1372:683676] 0
2018-04-03 14:17:22.679816+0800 GCD演示[1372:683678] 0
2018-04-03 14:17:22.679810+0800 GCD演示[1372:682541] 0
2018-04-03 14:17:22.679866+0800 GCD演示[1372:683679] 0
2018-04-03 14:17:22.680138+0800 GCD演示[1372:683678]  {number = 3, name = (null)}come here!
2018-04-03 14:17:22.680170+0800 GCD演示[1372:683680] 5385
2018-04-03 14:17:22.680480+0800 GCD演示[1372:683681] 5385
2018-04-03 14:17:22.680785+0800 GCD演示[1372:683676] 来了 ----1 {number = 6, name = (null)}
2018-04-03 14:17:22.680785+0800 GCD演示[1372:682541] 来了 ----0 {number = 5, name = (null)}
2018-04-03 14:17:22.680795+0800 GCD演示[1372:683678] 来了 ----2 {number = 3, name = (null)}
2018-04-03 14:17:22.680805+0800 GCD演示[1372:683680] 来了 ----4 {number = 4, name = (null)}
2018-04-03 14:17:22.680813+0800 GCD演示[1372:683679] 来了 ----3 {number = 7, name = (null)}
2018-04-03 14:17:22.680992+0800 GCD演示[1372:683681] 来了 ----5 {number = 8, name = (null)}
2018-04-03 14:17:22.681220+0800 GCD演示[1372:683682] -1
2018-04-03 14:17:22.681490+0800 GCD演示[1372:683683] -1
2018-04-03 14:17:22.681692+0800 GCD演示[1372:683676] -1
2018-04-03 14:17:22.681766+0800 GCD演示[1372:683684] -1
2018-04-03 14:17:22.683852+0800 GCD演示[1372:683682] 来了 ----6 {number = 9, name = (null)}
2018-04-03 14:17:22.684087+0800 GCD演示[1372:683683] 来了 ----7 {number = 10, name = (null)}
2018-04-03 14:17:22.684468+0800 GCD演示[1372:683676] 来了 ----8 {number = 6, name = (null)}
2018-04-03 14:17:22.685031+0800 GCD演示[1372:683684] 来了 ----9 {number = 11, name = (null)}

onceToken的值一开始是0,执行到一次性语句时变为5385(或许是其他什么数),执行完毕变为-1,什么原理还望大神的指点。
还有一种一次性执行的方式:互斥锁 @synchronized(),在单例中用到过,下面将两种单例创建的方式列举一下,并比较两种方式创建单例的效率如何:

@synchronized():

#import "Singleton.h"
static Singleton *_instance;
@implementation Singleton
+ (instancetype)share{
    return [[self alloc]init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    @synchronized(self){
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
    return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
    return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
    return _instance;
}
@end

onceToken:

#import "Singleton2.h"
static Singleton2 *_instance;

@implementation Singleton2
+ (instancetype)share{
    return [[self alloc]init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    });
    return _instance;
}
- (id)copyWithZone:(NSZone *)zone{
    return _instance;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
    return _instance;
}
@end

苹果推荐使用GCD的一次性,效率高,而互斥锁效率低,下面比较一下两种方式的执行时间就一目了然了:

    //@synchronized
    //获取代码开始执行时时间
    CFAbsoluteTime synBegin =CFAbsoluteTimeGetCurrent();
    //获取代码结束执行时时间
    Singleton *synSingle = [Singleton share];
    CFAbsoluteTime synEnd =CFAbsoluteTimeGetCurrent();
    //计算开始和结束的时间差,该时间差就是循环创建单例需要的时间
    NSLog(@"synchronized------%f",synBegin- synEnd);
    
    //onceToken
    //获取代码开始执行时时间
    CFAbsoluteTime onceBegin =CFAbsoluteTimeGetCurrent();
    //获取代码结束执行时时间
    Singleton2 *onceSingle = [Singleton share];
    CFAbsoluteTime onceEnd =CFAbsoluteTimeGetCurrent();
    //计算开始和结束的时间差,该时间差就是循环创建单例需要的时间
    NSLog(@"onceToken---------%f",onceBegin- onceEnd);

打印结果

2018-04-03 15:18:01.046723+0800 GCD演示[1580:939403] synchronized-------0.000021
2018-04-03 15:18:01.046944+0800 GCD演示[1580:939403] onceToken----------0.000004
  1. 调度组 dispatch_group_t
    GCD头文件group.h中谈到,可以将一组block提交到调度组(dispatch_group)中,执行逐个串行回调,下面来看看相关函数。
  • dispatch_group_t dispatch_group_create(void);
    创建一个调度组,释放调度组使用dispatch_release()函数,创建成功返回一个dispatch_group调度组,失败则返回NULL.

  • void dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
    提交一个闭包函数(block)到queue中,并关联到指定的group调度组.通过typedef void (^dispatch_block_t)(void);我们可以发现,该函数无法给block传递参数.
    1.group 指定的调度组,block的关联调度组。
    2.queue 提交闭包函数(block)的队列。
    3.block 提交到指定queue的闭包函数block。

  • void dispatch_group_async_f(dispatch_group_t group,dispatch_queue_t queue,void *_Nullable context,dispatch_function_t work);
    提交一个函数指针(dispatch_function_t)到queue中,并关联到指定的group调度组,函数返回void.
    1.group 指定的调度组,block的关联调度组。
    2.queue 提交闭包函数(block)的队列。
    3.context 传递到函数中的的参数。
    4.work 在指定的queue中的指定函数。

  • long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
    执行等待,等待所有关联到group调度组的block执行完成,或者等待timeout发生超时,当在超时时间timeout内执行完了所有的block函数,则返回0,否则返回非0值。
    1.group 给定调度组
    2.timeout 如果group调度组里边的block执行时间非常长,函数的等待时间.

  • void dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
    该函数指定了一个block,当group调度组里边的所有block都执行完成时,将通知block关联到group中,并加入到给定的queue队列里,当group调度组当前没有任何block关联的时候将立即将block提交到queue队列,并与group调度组关联,该函数返回void.
    1.group 给定的调度组
    2.queue 给定的队列.
    3.给定的闭包函数.

  • void dispatch_group_notify_f(dispatch_group_t group,dispatch_queue_t queue,void *_Nullable context,dispatch_function_t work);
    与disptch_group_notify类似,提交的一个函数work作为执行体,context是执行时传递的参数,该函数返回void.

  • void dispatch_group_enter(dispatch_group_t group);

  • void dispatch_group_leave(dispatch_group_t group);
    这一对函数调用一次意味着非使用dispatch_group_async方式,将一个block提交到指定的queue上并关联到group调度组.两个函数必须成对出现。

在实际开发中,需要开启N个异步线程,但是后续操作,需要依赖N个线程返回的数据,需要接收所有线程任务执行完成的通知。

    //1.对列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    //2.调度组
    dispatch_group_t group = dispatch_group_create();
    //3.添加任务,让队列调度,任务执行情况,最后通知群组
    dispatch_group_async(group, q, ^{
        NSLog(@"Download A%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, q, ^{
        sleep(1.0);
        NSLog(@"Download B%@",[NSThread currentThread]);
    });
    dispatch_group_async(group, q, ^{
        NSLog(@"Download C%@",[NSThread currentThread]);
    });
    //所有任务完成之后通知群组
    //用调度组,可以监听全局队列的任务,主队列去执行最后的任务
    //dispatch_group_notify 本身也是异步执行
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@" OK %@",[NSThread currentThread]);
    });
    NSLog(@"come here");

注:dispatch_group_notify这个函数是异步的,如果要换成同步用dispatch_group_wait(group, DISPATCH_TIME_FOREVER).群组不空,这句代码一直等,下面代码不执行

// 队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    // 调度组
    dispatch_group_t group = dispatch_group_create();
    
    // 1. 进入群组,给 group 打一个标记,在后续紧接着的 block 归 group 监听
    // dispatch_group_enter 和 dispatch_group_leave 必须成对出现!
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:10];
        NSLog(@"download A - %@", [NSThread currentThread]);
        // 耗时操作代码

        // 2. 离开群组
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"download B - %@", [NSThread currentThread]);
        // 耗时操作代码

        // 2. 离开群组
        dispatch_group_leave(group);
    });
    
    // 等待群组空,一直到永远,群组不空,这句代码就死等,同步
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"OK");
  1. 主队列 dispatch_get_main_queue()
    主队列是用来在主线程上调度任务,会在程序开始时创建,只需要获取。
    异步任务
    //主队列 --> 已启动主线程就可以拿到主队列
    dispatch_queue_t q = dispatch_get_main_queue();
    //异步任务
    dispatch_async(q, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
    for (int i = 0; i < 10 ; i ++) {
        NSLog(@"come here");
    }

同步任务会造成死锁

    //崩溃!!!!!!
    dispatch_sync(q, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
    for (int i = 0; i < 10 ; i ++) {
        NSLog(@"come here");
    }

解决办法

    //主队列同步任务(不死锁)
    void (^task)() = ^{
        dispatch_queue_t q = dispatch_get_main_queue();
        //2.异步任务
        dispatch_sync(q, ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
        for (int i = 0; i < 10 ; i ++) {
            NSLog(@"come here %@",[NSThread currentThread]);
        }
    };
    dispatch_async(dispatch_get_global_queue(0, 0), task);
更新中。。。。。。

你可能感兴趣的:(iOS进阶之多线程--GCD)