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] <NSThread: 0x6040000687c0>{number = 1, name = main}   0
2018-04-03 11:53:46.883576+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   1
2018-04-03 11:53:46.883763+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   2
2018-04-03 11:53:46.883928+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   3
2018-04-03 11:53:46.884098+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   4
2018-04-03 11:53:46.884260+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   5
2018-04-03 11:53:46.884390+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   6
2018-04-03 11:53:46.884529+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   7
2018-04-03 11:53:46.884648+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{number = 1, name = main}   8
2018-04-03 11:53:46.884789+0800 GCD演示[1099:402251] <NSThread: 0x6040000687c0>{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] <NSThread: 0x600000274c40>{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] <NSThread: 0x600000274c40>{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] <NSThread: 0x600000274c40>{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] <NSThread: 0x600000274c40>{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] <NSThread: 0x600000274c40>{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] <NSThread: 0x600000274c40>{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] <NSThread: 0x600000274c40>{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] <NSThread: 0x600000274c40>{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] <NSThread: 0x600000274c40>{number = 3, name = (null)}   8
2018-04-03 11:58:17.744776+0800 GCD演示[1126:426370] <NSThread: 0x600000274c40>{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] <NSThread: 0x604000471a80>{number = 4, name = (null)}   1
2018-04-03 13:36:05.458008+0800 GCD演示[1213:500169] <NSThread: 0x60000027ee40>{number = 3, name = (null)}   0
2018-04-03 13:36:05.458010+0800 GCD演示[1213:500171] <NSThread: 0x604000471300>{number = 6, name = (null)}   3
2018-04-03 13:36:05.458012+0800 GCD演示[1213:500180] <NSThread: 0x60000027eb80>{number = 5, name = (null)}   2
2018-04-03 13:36:05.458301+0800 GCD演示[1213:500170] <NSThread: 0x604000471a80>{number = 4, name = (null)}   5
2018-04-03 13:36:05.458310+0800 GCD演示[1213:500168] <NSThread: 0x60000027f200>{number = 7, name = (null)}   4
2018-04-03 13:36:05.458323+0800 GCD演示[1213:500169] <NSThread: 0x60000027ee40>{number = 3, name = (null)}   6
2018-04-03 13:36:05.458345+0800 GCD演示[1213:500171] <NSThread: 0x604000471300>{number = 6, name = (null)}   7
2018-04-03 13:36:05.458451+0800 GCD演示[1213:500168] <NSThread: 0x60000027f200>{number = 7, name = (null)}   8
2018-04-03 13:36:05.458476+0800 GCD演示[1213:500170] <NSThread: 0x604000471a80>{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] <NSThread: 0x6000000792c0>{number = 1, name = main}   0
2018-04-03 13:37:58.162436+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   1
2018-04-03 13:37:58.163300+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   2
2018-04-03 13:37:58.163621+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   3
2018-04-03 13:37:58.163761+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   4
2018-04-03 13:37:58.163916+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   5
2018-04-03 13:37:58.164035+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   6
2018-04-03 13:37:58.164410+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   7
2018-04-03 13:37:58.164839+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{number = 1, name = main}   8
2018-04-03 13:37:58.165329+0800 GCD演示[1228:510822] <NSThread: 0x6000000792c0>{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] 用户登录----- <NSThread: 0x604000072580>{number = 1, name = main}
2018-04-03 13:43:05.538480+0800 GCD演示[1264:539460] 用户支付----- <NSThread: 0x604000072580>{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] 用户下载----- <NSThread: 0x60400027d080>{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--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.628785+0800 GCD演示[1297:579087] 用户登录----- <NSThread: 0x60000027b940>{number = 3, name = (null)}
2018-04-03 13:54:32.629000+0800 GCD演示[1297:579094] 1--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.629150+0800 GCD演示[1297:579087] 用户支付----- <NSThread: 0x60000027b940>{number = 3, name = (null)}
2018-04-03 13:54:32.629169+0800 GCD演示[1297:579094] 2--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.629267+0800 GCD演示[1297:579094] 3--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.629269+0800 GCD演示[1297:579087] 用户下载----- <NSThread: 0x60000027b940>{number = 3, name = (null)}
2018-04-03 13:54:32.629835+0800 GCD演示[1297:579094] 4--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.630147+0800 GCD演示[1297:579094] 5--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.630346+0800 GCD演示[1297:579094] 6--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.630643+0800 GCD演示[1297:579094] 7--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.631249+0800 GCD演示[1297:579094] 8--- <NSThread: 0x604000273700>{number = 4, name = (null)}
2018-04-03 13:54:32.631599+0800 GCD演示[1297:579094] 9--- <NSThread: 0x604000273700>{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]  <NSThread: 0x60000026f180>{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 <NSThread: 0x604000470480>{number = 6, name = (null)}
2018-04-03 14:17:22.680785+0800 GCD演示[1372:682541] 来了 ----0 <NSThread: 0x60000026ec00>{number = 5, name = (null)}
2018-04-03 14:17:22.680795+0800 GCD演示[1372:683678] 来了 ----2 <NSThread: 0x60000026f180>{number = 3, name = (null)}
2018-04-03 14:17:22.680805+0800 GCD演示[1372:683680] 来了 ----4 <NSThread: 0x604000470a40>{number = 4, name = (null)}
2018-04-03 14:17:22.680813+0800 GCD演示[1372:683679] 来了 ----3 <NSThread: 0x604000470800>{number = 7, name = (null)}
2018-04-03 14:17:22.680992+0800 GCD演示[1372:683681] 来了 ----5 <NSThread: 0x600000268680>{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 <NSThread: 0x60000026fa80>{number = 9, name = (null)}
2018-04-03 14:17:22.684087+0800 GCD演示[1372:683683] 来了 ----7 <NSThread: 0x60000026f980>{number = 10, name = (null)}
2018-04-03 14:17:22.684468+0800 GCD演示[1372:683676] 来了 ----8 <NSThread: 0x604000470480>{number = 6, name = (null)}
2018-04-03 14:17:22.685031+0800 GCD演示[1372:683684] 来了 ----9 <NSThread: 0x6040004711c0>{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);
更新中。。。。。。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容