iOS开发之多线程编程总结(二)

背景

担心了两周的我终于轮到去医院做胃镜检查了!去的时候我都想好了最坏的可能(胃癌),之前在网上查的症状都很相似。最后检查结果出来终于安心了,诊断结果:慢性非萎缩性胃炎(胃窦为主)

我是一个心里素质不过关的人,所以说对待问题的时候可能会有一种悲观的想法。朋友说我本来可能没病都被自己吓出病了,这是一个心态问题。

你们可能问我做胃镜什么感觉?我只能告诉你一个字:真爽,具体只能自己去感受。

自己眼中的自己.jpg

保持乐观的心态

看完下面的笑话就要开始我们的装逼之旅了_

  1. 一个大学生去公司实习,老板让他先从扫地开始。大学生:“我可是大学生哎……”老板:“哦,对了,我差点忘了你是大学生,来来来,我教你怎么扫地”
  1. 一天,老师让同学们写作文,题目是 《我的理想》。
    小明在作文里写道:我长大了要去抢银行,然后把钱分给穷苦老百姓。
    第二天老师改完了,写给小明的评语是这样的:很不错的理想,分钱的时候不要忘了老师,但你要注意你的同桌,他说他长大了要去当警察。

GCD基本介绍

  • GCD(Grand Central Dispatch)iOS 4.0开始引入的新多线程编程功能,
  • GCD(Grand Central Dispatch)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。
  • GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,是完全面向过程的。

GCD基本概念

这就需要上一篇博客里的基本知识了(不清楚去看下)iOS开发之多线程编程总结(一)

任务和队列

  • 任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行异步执行。两者的主要区别是:是否具备开启新线程的能力。

    1. 同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
    • 必须等待当前语句执行完毕,才会执行下一条语句
    • 不会开启线程
    • 在当前主线程执行 block 的任务
    • dispatch_sync(queue, block);
    1. ** 异步执行(async)**:可以在新的线程中执行任务,具备开启新线程的能力
    • 不用等待当前语句执行完毕,就可以执行下一条语句
    • 会开启线程执行 block 的任务
    • 异步是多线程的代名词
    • dispatch_async(queue, block);
  • 队列:这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有四种队列:串行队列并发队列主队列全局队列

    1. 串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

      • 一次只能"调度"一个任务
      • dispatch_queue_create("queue", NULL);
        或者dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    2. 并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),

      • 一次可以"调度"多个任务
      • 并发功能只有在异步(dispatch_async)函数下才有效
      • dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    3. 主队列

      • 专门用来在主线程上调度任务的队列
      • 不会开启线程
      • 在主线程空闲时才会调度队列中的任务在主线程执行
      • dispatch_get_main_queue();
    4. 全局队列

      • 执行过程和并发队列一致,参考并发队列
      • dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

小结: 在以后的使用中,记住下面的就可以了!

  1. 开不开线程由执行任务的函数决定
  • 异步开,异步是多线程的代名词
  • 同步不开
  1. 开几条线程由队列决定
  • 串行队列开一条线程(GCD会开一条,NSOperation Queue最大并发数为1时也可能开多条)
  • 并发队列开多条线程,具体能开的线程数量由底层线程池决定

GCD的使用

今天我们学习下面图片的相关知识点,Demo下载链接会在文章最后给出来


GCD知识点.png

简单来看一段代码:异步下载图片

#pragma mark - 1.异步下载图片
- (IBAction)downLoadAction:(UIButton *)sender {
    self.imageView.image = nil;
    //获取全局队列
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //异步下载图片
        NSURL *url=[NSURL URLWithString:@"https://p1.bpimg.com/524586/475bc82ff016054ds.jpg"];
        //将资源转换为二进制
        NSData *data=[NSData dataWithContentsOfURL:url];
        //将二进制转化为图片
        UIImage *image=[UIImage imageWithData:data];
        
        //获取主队列,更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            //给图片控件赋值
            self.imageView.image=image;
        });
    });
}

NSThread对比可以发现

  • 所有的代码写在一起的,让代码更加简单,易于阅读和维护
  • NSThread 通过 @selector 指定要执行的方法,代码分散
  • GCD 通过 block 指定要执行的代码,代码集中
  • 使用 GCD 不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期
  • 如果要开多个线程 NSThread 必须实例化多个线程对象或者使用分类方法
  • NSThread 靠 NSObject 的分类方法实现的线程间通讯,GCD 靠 block
  • dispatch_async(queue, block);就是异步执行一个队列里面的任务block。每个block之间是异步执行的,但是block里面的代码是顺序执行的!
  • dispatch_sync(queue, block);就是同步执行一个队列里面的任务block

1. 串行队列(Serial Dispatch Queue)

串行队列的创建:
 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
 dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
串行队列同步和异步执行Demo:
 #pragma mark - 串行队列同步和串行队列异步
 //串行队列同步
 - (void)serialQueueSyncMethod{
     //创建队列
     dispatch_queue_t queue = dispatch_queue_create("serialQueueSyncMethod", DISPATCH_QUEUE_SERIAL);
     //执行任务
     for (int i = 0; i < 6; i++) {
         NSLog(@"mainThread--->%d",i);
         dispatch_sync(queue, ^{
             NSLog(@"Current Thread=%@---->%d-----",[NSThread currentThread],i);
         });
     }
     NSLog(@"串行队列同步end");
  }

 //串行队列异步
  - (void)serialQueueAsyncMethod{
     dispatch_queue_t queue = dispatch_queue_create("serialQueueAsyncMethod", DISPATCH_QUEUE_SERIAL);
     for (int i = 0; i < 6; i++) {
         NSLog(@"mainThread--->%d",i);
         dispatch_async(queue, ^{
             NSLog(@"Current Thread=%@---->%d-----",[NSThread currentThread],i);
         });
     }
     NSLog(@"串行队列异步end");
  }   
串行队列 同步执行结果:
2016-11-03 17:16:35.794 ThreadDemo[27088:5268309] mainThread--->0
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->0-----
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] mainThread--->1
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->1-----
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] mainThread--->2
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->2-----
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] mainThread--->3
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->3-----
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] mainThread--->4
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->4-----
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] mainThread--->5
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->5-----
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] 串行队列同步end
串行队列 异步执行结果:
2016-11-03 17:22:25.074 ThreadDemo[27122:5273206] mainThread--->0
2016-11-03 17:22:25.074 ThreadDemo[27122:5273206] mainThread--->1
2016-11-03 17:22:25.074 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->0-----
2016-11-03 17:22:25.074 ThreadDemo[27122:5273206] mainThread--->2
2016-11-03 17:22:25.074 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->1-----
2016-11-03 17:22:25.074 ThreadDemo[27122:5273206] mainThread--->3
2016-11-03 17:22:25.075 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->2-----
2016-11-03 17:22:25.075 ThreadDemo[27122:5273206] mainThread--->4
2016-11-03 17:22:25.075 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->3-----
2016-11-03 17:22:25.075 ThreadDemo[27122:5273206] mainThread--->5
2016-11-03 17:22:25.075 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->4-----
2016-11-03 17:22:25.075 ThreadDemo[27122:5273206] 串行队列异步end
2016-11-03 17:22:25.075 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->5-----
小结:
  • 从串行队列同步执行结果看出打印是交替执行的!从打印中看到number=1,说明线程block任务是在主线程中执行的。因为同步是不会开辟线程的,所有当前只有一个主线程MainThread。也就是说串行队列同步执行不会开辟线程,所有block任务之间是同步执行的
  • 从串行队列异步执行结果看出打印并不是交替执行的!从打印中看到number=5,说明线程block的任务是在一个全新的线程中执行的。因为异步是会开辟线程的,所有当前有主线程MainThread和子线程number=5。也就是说串行队列异步执行会仅会开辟一个新的线程,所有block任务之间是同步执行的
  • 以先进先出的方式,顺序调度队列中的任务执行
  • 无论队列中指定的任务函数是同步还是异步,都会等待前一个任务执行完毕以后,再调度后面的任务

2. 并发队列(Concurrent Dispatch Queue)

并发队列创建
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
并发队列同步和异步执行Demo
 #pragma mark - 并行队列同步和并行队列异步
 //并行队列同步
 - (void)concurrentQueueSyncMethod{
     dispatch_queue_t queue = dispatch_queue_create("concurrentQueueSyncMethod", DISPATCH_QUEUE_CONCURRENT);
    
     for (int i = 0; i < 6; i++) {
         dispatch_sync(queue, ^{
             NSLog(@"Current Thread=%@---->%d-----",[NSThread currentThread],i);
         });
     }
     NSLog(@"并行队列同步end");
  }

 //并行队列异步
- (void)concurrentQueueAsyncMethod{
     dispatch_queue_t queue = dispatch_queue_create("concurrentQueueAsyncMethod", DISPATCH_QUEUE_CONCURRENT);

     for (int i = 0; i < 6; i++) {
         dispatch_async(queue, ^{
             NSLog(@"Current Thread=%@---->%d-----",[NSThread currentThread],i);
         });
     }
    
     NSLog(@"并行队列异步end");
  }      
并发 队列同步执行结果:
2016-11-03 17:49:33.850 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->0-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->1-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->2-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->3-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->4-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->5-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] 并行队列同步end
并发队列 异步执行结果:
2016-11-03 18:33:32.794 ThreadDemo[27283:5311953] 并行队列异步end
2016-11-03 18:33:32.794 ThreadDemo[27283:5312009] Current Thread=<NSThread: 0x60800026b8c0>{number = 3, name = (null)}---->0-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312006] Current Thread=<NSThread: 0x600000269180>{number = 4, name = (null)}---->1-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312003] Current Thread=<NSThread: 0x600000268f80>{number = 6, name = (null)}---->3-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312174] Current Thread=<NSThread: 0x60800026b740>{number = 8, name = (null)}---->5-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312004] Current Thread=<NSThread: 0x60800026b7c0>{number = 5, name = (null)}---->2-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312173] Current Thread=<NSThread: 0x60800026b800>{number = 7, name = (null)}---->4-----
小结:
  • 并发队列同步执行和串行队列同步执行一样,都不会开辟新线程,block任务之间是同步执行的!
  • 并发队列异步执行结果中看到开辟了多个线程,并且执行顺序也不是顺序执行。因为异步开多线程的代名词,并发是开多条线程的代名词
  • 有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。
  • 以先进先出的方式,并发调度队列中的任务执行
  • 如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务
  • 如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行

3. 全局队列(Global Dispatch Queue)

全局队列基本知识
  • dispatch_get_global_queue函数来获取
  • 全局队列是所有应用程序都能够使用的并发队列(Concurrent Dispatch Queue),没必要通过dispatch_queue_create函数逐个生成并发队列,只需要获取Global Dispatch Queue即可
  • 是系统为了方便程序员开发提供的,其工作表现与并发队列一致其工作表现与并发队列一致其工作表现与并发队列一致
  • Global Dispatch Queue有4个优先级,分别是高优先级、默认优先级、低优先级、后台优先级!因为XNU内核用于Global Dispatch Queue的线程并不能保证实时性,因此执行优先级知识大概的判断和区分。
  #define DISPATCH_QUEUE_PRIORITY_HIGH 2
  #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
  #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
  #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
全局队列同步和异步执行Demo
 #pragma mark -全局队列同步和全局队列异步(工作表现与并发队列一致)
 //全局队列同步
 - (void)globalSyncMethod{
     //获取全局队列
       dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     //执行任务
     for (int i = 0; i < 10; ++i) {
         dispatch_sync(queue, ^{
             NSLog(@"global_queue_sync%@---->%d----",[NSThread currentThread],i);
         });
     }
     NSLog(@"global_queue_sync_end");
 }

 //全局队列异步
 - (void)globalAsyncMethod{
     //获取全局队列
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     //执行任务
     for (int i = 0; i < 10; ++i) {
         dispatch_async(queue, ^{
             NSLog(@"global_queue_async%@---->%d----",[NSThread currentThread],i);
         });
     }
     NSLog(@"global_queue_async_end");
 }
全局队列 同步执行结果:
2016-11-03 19:06:02.650 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->0----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->1----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->2----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->3----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->4----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->5----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->6----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->7----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->8----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->9----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync_end
全局队列 异步执行结果:
2016-11-03 19:12:00.242 ThreadDemo[27430:5333537] global_queue_async_end
2016-11-03 19:12:00.242 ThreadDemo[27430:5334057] global_queue_async<NSThread: 0x608000266100>{number = 5, name = (null)}---->0----
2016-11-03 19:12:00.242 ThreadDemo[27430:5334053] global_queue_async<NSThread: 0x600000263c80>{number = 6, name = (null)}---->1----
2016-11-03 19:12:00.242 ThreadDemo[27430:5334056] global_queue_async<NSThread: 0x600000263cc0>{number = 7, name = (null)}---->2----
2016-11-03 19:12:00.242 ThreadDemo[27430:5334063] global_queue_async<NSThread: 0x608000268500>{number = 8, name = (null)}---->3----
2016-11-03 19:12:00.242 ThreadDemo[27430:5334064] global_queue_async<NSThread: 0x600000263b80>{number = 9, name = (null)}---->4----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334057] global_queue_async<NSThread: 0x608000266100>{number = 5, name = (null)}---->5----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334053] global_queue_async<NSThread: 0x600000263c80>{number = 6, name = (null)}---->7----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334065] global_queue_async<NSThread: 0x600000073600>{number = 10, name = (null)}---->6----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334056] global_queue_async<NSThread: 0x600000263cc0>{number = 7, name = (null)}---->8----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334066] global_queue_async<NSThread: 0x608000267d40>{number = 11, name = (null)}---->9----

4. 主队列(Main Dispatch Queue)

主队列基本知识
  • dispatch_get_main_queue()函数来获取
  • 专门用来在主线程上调度任务的队列
  • 不会开启线程
  • 以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行
  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
主队列同步和异步执行Demo
 #pragma mark -主队列同步和主队列异步
 //主队列同步
 - (void)mainSyncMethod{
     //获取主队列
     dispatch_queue_t queue = dispatch_get_main_queue();
     //执行任务
     for (int i = 0; i < 10; ++i) {
         dispatch_sync(queue, ^{
             NSLog(@"main_queue_sync%@---->%d----",[NSThread currentThread],i);
         });
     }
     NSLog(@"main_queue_sync_end");
 }

 //主队列异步
 - (void)mainAsyncMethod{
     //获取主队列
     dispatch_queue_t queue = dispatch_get_main_queue();
     //执行任务
     for (int i = 0; i < 10; ++i) {
         dispatch_async(queue, ^{
            NSLog(@"main_queue_async%@---->%d----",[NSThread currentThread],i);
         });
     }
     NSLog(@"main_queue_async_end");
 }
主队列 同步执行结果:
主线程和主队列相互等待造成死锁,程序会直接卡死!
原因:源代码在Main Dispatch Queue 即主队列中执行指定的block任务,并等待其结束。而其实在主线程中正在执行这些源代码,所以无法执行追加到Main Dispatch Queue 的block任务。
下面例子也一样:
dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"main_queue_sync%@----",[NSThread currentThread]);
        });
    });

主队列 异步执行结果:
2016-11-03 19:45:38.154 ThreadDemo[27501:5349956] main_queue_async_end
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->0----
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->1----
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->2----
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->3----
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->4----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->5----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->6----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->7----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->8----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->9----

5.延迟执行(dispatch_after)

  • 在GCD中我们使用dispatch_after()函数来延迟执行队列中的任务, dispatch_after()是异步执行队列中的任务的,也就是说使用dispatch_after()来执行队列中的任务不会阻塞当前任务。等到延迟时间到了以后就会开辟一个新的线程然后执行队列中的任务。
  • dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);来创建
  • 经过我的猜测测试(看不到源码),你们也思考一下这个延迟函数的实现过程。底层实现应该是dispath_async函数追加block到Main Dispatch Queue等相应队列,伪代码如下(以Main Dispatch Queue为例):
 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
 delay(2){
 dispatch_async(queue, ^{
     dispatch_async(dispatch_get_main_queue(), ^{
     add task to mainQueue
    });
  });
 }
延迟执行(dispatch_after)Demo
#pragma mark -  延迟执行
 - (void)GCDAfterRunMethod{
     // 循环5次
     for (int i =0; i < 10000; i++ ) {
         NSLog(@"let's go %d",i);
         // 设置2秒后执行block
         dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
         dispatch_after(time, dispatch_get_main_queue(), ^{
             NSLog(@"This is my %d number!%@",i,[NSThread currentThread]);
         });
     }
       //等价于下面实现
 //    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
 //    for (int i = 0; i < 10000; i++) {  //        NSLog(@"let's go %d",i);
 //        dispatch_async(queue, ^{
 //           dispatch_async(dispatch_get_main_queue(), ^{  //                NSLog(@"This is my %d number!%@",i,[NSThread currentThread]);
 //           });
 //        });
 //    }
 }
注意:
  • dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);
  • 第二个参数:指定要追加处理的Dispatch Queue
  • 第三个参数:指定要执行处理的Block
  • 第一个参数:指定时间用的dispatch_time_t类型的值,该值是使用dispatch_time函数或者dispatch_walltime函数生成
    1.dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
    里面有两个参数:
  参数1获取指定的时间开始,通常为DISPATCH_TIME_NOW,定义:
  #define DISPATCH_TIME_NOW (0ull)//现在
  #define DISPATCH_TIME_FOREVER (~0ull)//永远,意味着你用它你的任务永远不会执行了!
参数2是从参数1延迟指定的时间来执行任务,也是一个时间
#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
ull是C语言的数值字面量,是显示表明类型是使用的字符串(表示“unsigned long long”)
NSEC_PER_SEC 是秒的单位数量级 等价于1亿纳秒
上面代码中提到的(int64_t)(2 * NSEC_PER_SEC))就是2秒了

2.使用dispatch_walltime函数生成dispatch_time_t,用于计算绝对时间,例如在dispatch_after函数中指定2016年11月3日21时45分1秒这一绝对时间,可粗略的闹钟功能来使用。也就是到指定时间就会执行任务

//由NSDate类对象获取dispatch_time_t传递给dispatch_after函数使用
dispatch_time_t  getDispatchTimeByDate(NSDate *date){
NSTimeInterval interval;
double second,subSecond;
struct timespec time;
dispatch_time_t milestone;
   
interval = [date timeIntervalSince1970];
   
subSecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subSecond * NSEC_PER_SEC;

milestone = dispatch_walltime(&time, 0);
return milestone;
}

6. 更改优先级(dispatch_set_target_queue)

  • dispatch_queue_create函数生成的Dispatch Queue不管是 串行队列(Serial Dispatch Queue) 还是 并发队列(Concurrent Dispatch Queue) ,都是用与 默认优先级全局队列(Global Dispatch Queue)相同执行优先级的线程。而全局队列工作方式与并发队列工作方式完全一致!

  • dispatch_set_target_queue(dispatch_object_t object,dispatch_queue_t _Nullable queue);函数来更改队列优先级

    • 第一个参数:指定要变更优先级的队列(要更改队列)
    • 第二个参数:指定第一个参数(队列)要和我们预期队列执行相同优先级的队列(目标队列)

1. dispatch_set_target_queue第一个Demo:

 - (void)GCDSetTargetQueueMethod{
    
     dispatch_queue_t targetQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    
     dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
     dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
      //更改优先级
     dispatch_set_target_queue(queue1, targetQueue);
    
     for (int i = 0; i < 6; i++) {
         dispatch_async(queue1, ^{
         NSLog(@"queue1-currentThread = %@-->%d",[NSThread currentThread],i);
         });
     }
     for (int i = 0; i < 6; i++) {
          dispatch_async(queue2, ^{
          NSLog(@"queue2-----currentThread = %@----->%d",[NSThread currentThread],i);
          });
     }
 } 
打印结果:
2016-11-04 11:14:57.034 ThreadDemo[28477:5530919] queue2-----currentThread = <NSThread: 0x60000007e0c0>{number = 11, name = (null)}----->1
2016-11-04 11:14:57.034 ThreadDemo[28477:5530920] queue2-----currentThread = <NSThread: 0x60000007a140>{number = 12, name = (null)}----->2
2016-11-04 11:14:57.034 ThreadDemo[28477:5530918] queue2-----currentThread = <NSThread: 0x60000007a100>{number = 10, name = (null)}----->0
2016-11-04 11:14:57.034 ThreadDemo[28477:5530919] queue2-----currentThread = <NSThread: 0x60000007e0c0>{number = 11, name = (null)}----->3
2016-11-04 11:14:57.034 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->0
2016-11-04 11:14:57.034 ThreadDemo[28477:5530920] queue2-----currentThread = <NSThread: 0x60000007a140>{number = 12, name = (null)}----->5
2016-11-04 11:14:57.034 ThreadDemo[28477:5530921] queue2-----currentThread = <NSThread: 0x60000007c340>{number = 13, name = (null)}----->4
2016-11-04 11:14:57.035 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->1
2016-11-04 11:14:57.036 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->2
2016-11-04 11:14:57.036 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->3
2016-11-04 11:14:57.036 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->4
2016-11-04 11:14:57.037 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->5
打印解释:
  • queue1和queue2设置的目标队列是全局队列(并发),也就是允许要更改的队列可以开辟多条线程(可以超过一条)
  • 代码中queue1变更和低优先级的全局队列一样优先级的串行队列,queue2是dispatch_queue_create创建和默认优先级的全局队列一样优先级的并发队列。
  • queue1和queue2都是异步执行,都会开辟线程,queue2是并发队列所以打印中看到有多个线程,并且任务之间也不是顺序执行。queue1是串行队列,所以只会开辟一个线程,任务会顺序执行。
  • queue2队列的优先级比queue1队列的优先级要高,从打印中可以看到queue2的确比queue1任务要普遍先执行。为什么要说普遍呢,因为从结果上看到queue1有一个任务是在queue2里任务没有执行完毕也执行了,为什么会出现这个问题呢??
在上面全局队列中说到了优先级的事情
因为XNU内核用于Global Dispatch Queue的线程并不能保证实时性,因此执行优先级知识大概的判断和区分,
所有我们不能完全依赖这个优先级来做队列的顺序事情,否则会出现问题!切记

2. dispatch_set_target_queue第一个Demo:

  • dispatch_set_target_queue除了能用来设置队列的优先级之外,还能够创建队列的执行层次
    ,当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的target指向新创建的队列即可,比如:


    队列的执行层次.png
 - (void)GCDSetTargetQueueMethod{
     dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);//目标队列
     dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行队列
     dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);//并发队列
     //设置参考
     dispatch_set_target_queue(queue1, targetQueue);
     dispatch_set_target_queue(queue2, targetQueue);
    
     for (int i = 0; i < 6; i++) {
       dispatch_async(queue1, ^{
           NSLog(@"queue1-currentThread = %@-->%d",[NSThread currentThread],i);
       });
   }
   for (int i = 0; i < 6; i++) {
         dispatch_async(queue2, ^{
             NSLog(@"queue2-currentThread = %@-->%d",[NSThread currentThread],i);
         });
     }
    
 }
打印结果:
2016-11-04 11:34:34.722 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->0
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->1
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->2
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->3
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->4
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->5
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->0
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->1
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->2
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->3
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->4
2016-11-04 11:34:34.725 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->5
打印解释:
  • queue1和queue2设置的目标队列是串行队列,也就是允许要更改的队列可以开辟一条线程。
  • queue1和queue2优先级一样,他两执行顺序相当于一个串行队列异步执行!
  • queue1和queue2队列以targetQueue队列为参照对象,那么queue1和queue2中的任务将按照targetQueue的队列处理。
  • 适用场景:一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。这时候dispatch_set_target_queue将起到作用。

** dispatch_set_target_queue小结:**

  • dispatch_set_target_queue可以更改Dispatch Queue优先级。
  • dispatch_set_target_queue可以更改队列的执行层次,队列里的任务将会按照目标队列(target Queue)的队列来处理

7. 任务组Dispatch Group

  • GCD的任务组在开发中是经常被使用到,当你一组任务结束后再执行一些操作时,使用任务组在合适不过了。dispatch_group的职责就是当队列中的所有任务都执行完毕后在去做一些操作,也就是说在任务组中执行的队列,当队列中的所有任务都执行完毕后就会发出一个通知来告诉用户任务组中所执行的队列中的任务执行完毕了。关于将队列放到任务组中执行有两种方式,一种是使用dispatch_group_async()函数,将队列与任务组进行关联并自动执行队列中的任务。另一种方式是手动的将队列与组进行关联然后使用异步将队列进行执行,也就是dispatch_group_enter()dispatch_group_leave()方法的使用。下方就给出详细的介绍。
1.队列与组自动关联并执行
  • 首先我们来介绍dispatch_group_async()函数的使用方式,该函数会将队列与相应的任务组进行关联,并且自动执行。当与任务组关联的队列中的任务都执行完毕后,会通过dispatch_group_notify()函数发出通知告诉用户任务组中的所有任务都执行完毕了。使用通知的方式是不会阻塞当前线程的,如果你使用dispatch_group_wait()函数,那么就会阻塞当前线程,直到任务组中的所有任务都执行完毕。
 //自动执行任务组
 - (void)GCDAutoDispatchGroupMethod{
     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_group_t group = dispatch_group_create();
   
     for (int i = 0; i < 6; i++) {
           dispatch_group_async(group, queue, ^{
             NSLog(@"current Thread = %@----->%d",[NSThread currentThread],i);
         });
     }

     dispatch_group_notify(group, dispatch_get_main_queue(), ^{
         NSLog(@"current Thread = %@----->这是最后执行",[NSThread   currentThread]);
     });
}
打印结果:
2016-11-04 15:11:34.010 ThreadDemo[29225:5641180] current Thread = <NSThread: 0x600000468200>{number = 5, name = (null)}----->2
2016-11-04 15:11:34.010 ThreadDemo[29225:5641193] current Thread = <NSThread: 0x60800007f900>{number = 3, name = (null)}----->0
2016-11-04 15:11:34.011 ThreadDemo[29225:5641438] current Thread = <NSThread: 0x608000467980>{number = 8, name = (null)}----->5
2016-11-04 15:11:34.010 ThreadDemo[29225:5641178] current Thread = <NSThread: 0x608000467640>{number = 6, name = (null)}----->3
2016-11-04 15:11:34.010 ThreadDemo[29225:5641177] current Thread = <NSThread: 0x6080004675c0>{number = 4, name = (null)}----->1
2016-11-04 15:11:34.010 ThreadDemo[29225:5641437] current Thread = <NSThread: 0x600000467f40>{number = 7, name = (null)}----->4
2016-11-04 15:11:34.011 ThreadDemo[29225:5641137] current Thread = <NSThread: 0x60800007e200>{number = 1, name = main}----->这是最后执行
  • 上面的函数就是使用dispatch_group_async()函数将队列与任务组进行关联并执行。首先我们创建了一个全局队列(并发),然后又创建了一个类型为dispatch_group_t的任务组group。使用dispatch_group_async()函数将两者进行关联并执行。使用dispatch_group_notify()函数进行监听group中队列的执行结果,如果执行完毕后,我们就在主线程中对结果进行处理。

  • dispatch_group_notify()函数有两个参数一个是发送通知的group,另一个是处理返回结果的队列。

  • dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);函数与dispatch_async函数相同,都追加block到指定的Dispatch Queue中,与dispatch_async不同的是指定生成的Dispatch Group为第一个参数。指定的block属于指定的Dispatch Group,不是Dispatch Queue,切记!

  • 无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可监视这些处理执行的结束。一旦检测到所有处理执行结束,就可将结束的处理追加到Dispatch Queue中。这就是使用Dispatch Group的原因。

  • 在追加到Dispatch Group中的处理全部执行结束时,代码中使用的dispatch_group_notify函数会将执行的Block追加到Dispatch Queue中,将第一个参数指定为要监视的Dispatch Group。在追加到该Dispatch Group的全部处理执行结束时,将第三个参数的Block追加到第二个参数的Dispatch Queue中。在dispatch_group_notify函数中不管指定什么样的Dispatch Queue,属于Dispatch Group的全部处理在追加指定的Block时都已执行结束。

  • 另外,在Dispatch Group中也可以使用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(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

  • dispatch_group_wait函数的第二个参数指定为等待时间(超时)。它属于dispatch_time_t类型的值。代码中使用的DISPATCH_TIME_FOREVER,意味着永久等待。只要属于Dispatch Group的操作尚未执行结束,就会一直等待,中途不能取消。

    指定等待时间为1微秒时,应做如下处理:

   //等待group处理结束
//    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
   dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1*USEC_PER_SEC));//1微秒
   long result = dispatch_group_wait(group, time);
   if (result == 0) {
       //属于Dispatch Group 的block任务全部处理结束
       NSLog(@"Dispatch Group全部处理完毕");

   }else{
       //属于Dispatch Group 的block任务还在处理中
       NSLog(@"Dispatch Group正在处理");
   }

  • 如果dispatch_group_wait函数的返回值不为0,就意味着虽然经过了指定的时间,但属于Dispatch Group的某一个处理还在执行中。如果返回值为0,那么全部处理执行结束。当等待时间为DISPATCH_TIME_FOREVER,由dispatch_group_wait函数返回时,属于Dispatch Group的处理必定全部执行结束,因此返回值恒为0。

  • 这里的“等待”是什么意思?这意味着一旦调用dispatch_group_wait函数,该函数就处于调用的状态而不返回。即执行dispatch_group_wait函数的现在的线程(当前线程)停止。在经过dispatch_group_wait函数中指定的时间或属于指定Dispatch Group的处理全部执行结束之前,执行该函数的线程停止,属于阻塞状态。

  • 指定DISPACH_TIME_NOW,则不用任何等待即可判定属于Dispatch Group的处理是否执行结束。

long result = diaptach_group_wait(group, DISPACH_TIME_NOW);
  • 在主线程的RunLoop的每次循环中,可检查执行是否结束,从而不消耗多余的等待时间,虽然这样有额可以,但一般在这种情况下,还是推荐用dispatch_group_notify函数追加结束处理到Main Dispatch Queue中。这是因为dispatch_group_notify函数可以简化代码。并且如果你用了diaptach_group_wait等待时间过长,中间不能取消队列任务这就很坑了!
2. 队列与组手动关联并执行
  • 接下来我们将手动的管理任务组与队列中的关系,也就是不使用dispatch_group_async()函数。我们使用dispatch_group_enter()dispatch_group_leave()函数将队列中的每次任务加入到到任务组中。首先我们使用dispatch_group_enter()函数进入到任务组中,然后异步执行队列中的任务,最后使用dispatch_group_leave()函数离开任务组即可。下面的函数中我们使用了dispatch_group_wait()函数,该函数的职责就是阻塞当前线程,来等待任务组中的任务执行完毕。该函数的第一个参数是所要等待的group,第二个参数是等待超时时间,此处我们设置的是DISPATCH_TIME_FOREVER,就说明等待任务组的执行永不超时,直到任务组中所有任务执行完毕。
//手动执行任务组
 - (void)GCDManualDispatchGroupMethod{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    for (int i = 0; i < 6; i++) {
        
        dispatch_group_enter(group);//进入队列组
        
        dispatch_async(queue, ^{
            NSLog(@"current Thread = %@----->%d",[NSThread currentThread],i);
            
            dispatch_group_leave(group);//离开队列组
        });
    }
    
    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//阻塞当前线程,直到所有任务执行完毕才会继续往下执行
    if (result == 0) {
        //属于Dispatch Group 的block任务全部处理结束
        NSLog(@"Dispatch Group全部处理完毕");

    }else{
        //属于Dispatch Group 的block任务还在处理中
        NSLog(@"Dispatch Group正在处理");
    }
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"current Thread = %@----->这是最后执行",[NSThread currentThread]);
    });
 }
打印结果:dispatch_group_wait()函数下方的print()函数在所有任务执行完毕之前是不会被调用的,因为dispatch_group_wait()会将当前线程进行阻塞。当然虽然是手动的将队列与任务组进行关联的,感觉display_group_notify()函数还是好用的。
2016-11-04 16:19:13.802 ThreadDemo[29402:5678525] current Thread = <NSThread: 0x600000271780>{number = 5, name = (null)}----->2
2016-11-04 16:19:13.802 ThreadDemo[29402:5678527] current Thread = <NSThread: 0x608000077300>{number = 3, name = (null)}----->0
2016-11-04 16:19:13.802 ThreadDemo[29402:5678524] current Thread = <NSThread: 0x60800007d840>{number = 4, name = (null)}----->1
2016-11-04 16:19:13.803 ThreadDemo[29402:5678760] current Thread = <NSThread: 0x600000271cc0>{number = 8, name = (null)}----->5
2016-11-04 16:19:13.802 ThreadDemo[29402:5678545] current Thread = <NSThread: 0x60800007d000>{number = 6, name = (null)}----->3
2016-11-04 16:19:13.802 ThreadDemo[29402:5678544] current Thread = <NSThread: 0x60800007d040>{number = 7, name = (null)}----->4
2016-11-04 16:19:13.803 ThreadDemo[29402:5678489] Dispatch Group全部处理完毕
2016-11-04 16:19:13.804 ThreadDemo[29402:5678489] current Thread = <NSThread: 0x600000069dc0>{number = 1, name = main}----->这是最后执行

8. 栅栏任务Dispatch_barrier_async

  • barrier顾名思义栅栏、障碍物的意思!
    在访问数据库或文件时,使用Serial Dispatch Queue可避免数据竞争的问题。

    写入处理确实不可与其他的写入处理以及包含读取处理的其他某些处理并行执行。但是如果读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。

    也就是说,为了高效率地进行访问,读取处理追加到Concurrent Dispatch Queue中,写入出路在任何一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在写入处理结束之前,读取处理不可执行)。

    虽然利用Dispatch Group和dispatch_set_target_queue函数也可实现,但代码会很复杂。有兴趣的可以自己尝试写写!
    CD为我们提供了更为聪明的解决办法——dispatch_barrier_async函数。该函数同dispatch_queue_create函数生成的Concurrent Dispatch Queue一起使用。

 #pragma mark - Dispatch_barrier_async
 - (void)GCDBarrierAsyncMethod{
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    void(^blk1_reading)(void) = ^{
         NSLog(@"blk1---reading");
    };
    
    void(^blk2_reading)(void) = ^{
        NSLog(@"blk2---reading");
    };
    void(^blk3_reading)(void) = ^{
        NSLog(@"blk3---reading");
    };
    void(^blk4_reading)(void) = ^{
        NSLog(@"blk4---reading");
    };
    void(^blk_writing)(void) = ^{
        NSLog(@"blk---writing");
    };
        
    dispatch_async(concurrentQueue, blk1_reading);
    dispatch_async(concurrentQueue, blk2_reading);
    
    //添加追加操作,,会等待b1和b2全部执行结束,执行完成追加操作b,才会继续并发执行下面操作
    dispatch_barrier_async(concurrentQueue, blk_writing);
    
    dispatch_async(concurrentQueue, blk3_reading);
    dispatch_async(concurrentQueue, blk4_reading);

}
打印结果:
2016-11-04 17:02:13.202 ThreadDemo[29492:5700974] blk2---reading
2016-11-04 17:02:13.202 ThreadDemo[29492:5700972] blk1---reading
2016-11-04 17:02:13.203 ThreadDemo[29492:5700972] blk---writing
2016-11-04 17:02:13.203 ThreadDemo[29492:5700972] blk3---reading
2016-11-04 17:02:13.203 ThreadDemo[29492:5700974] blk4---reading
  • 使用Concurrent Dispatch Queuedispatch_barrier_async函数可实现高效率的数据库访问和文件访问,dispatch_barrier_async函数还是比较好理解的。

9. 循环执行dispatch_apply

  • dispatch_apply()函数是用来循环来执行队列中的任务的,使用方式为:dispatch_apply(循环次数, 任务所在的队列) { 要循环执行的任务 }。使用该函数循环执行并行队列中的任务时,会开辟新的线程,不过有可能会在当前线程中执行一些任务。而使用dispatch_apply()执行串行队列中的任务时,会在当前线程中执行。无论是使用并行队列还是串行队列,dispatch_apply()都会阻塞当前执行函数线程。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_apply(10, queue, ^(size_t index){
     NSLog(@"%zu", index);
});
    
NSLog(@"done");
2016-11-04 17:24:25.819 ThreadDemo[29598:5715955] 2
2016-11-04 17:24:25.819 ThreadDemo[29598:5715904] 0
2016-11-04 17:24:25.819 ThreadDemo[29598:5716588] 1
2016-11-04 17:24:25.819 ThreadDemo[29598:5716640] 3
2016-11-04 17:24:25.820 ThreadDemo[29598:5715955] 4
2016-11-04 17:24:25.820 ThreadDemo[29598:5715904] 5
2016-11-04 17:24:25.820 ThreadDemo[29598:5716588] 6
2016-11-04 17:24:25.820 ThreadDemo[29598:5716640] 7
2016-11-04 17:24:25.820 ThreadDemo[29598:5715904] 9
2016-11-04 17:24:25.820 ThreadDemo[29598:5715955] 8
2016-11-04 17:24:25.820 ThreadDemo[29598:5715904] done
  • 输出结果中最后的done必定在最后的位置上,这是因为dispatch_apply函数会等待全部处理执行结束,也就是阻塞当前执行函数线程。

  • 另外,由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束(阻塞),因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。

#pragma mark - Dispatch_apply
 - (void)GCDDispatchApplyMethod{
   
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    void(^blk1_reading)(void) = ^{
        NSLog(@"blk1---reading");
    };
    
    void(^blk2_reading)(void) = ^{
        NSLog(@"blk2---reading");
    };
    void(^blk3_reading)(void) = ^{
        NSLog(@"blk3---reading");
    };
    void(^blk_writing)(void) = ^{
        NSLog(@"blk---writing");
    };

    NSMutableArray *array = [NSMutableArray new];
    [array addObject:blk1_reading];
    [array addObject:blk2_reading];
    [array addObject:blk3_reading];
    [array addObject:blk_writing];
    
    dispatch_async(queue, ^{
        dispatch_apply(array.count, queue, ^(size_t index) {
            void (^blk)(void) = [array objectAtIndex:index];
            blk();
            NSLog(@"%zu====%@",index,[array objectAtIndex:index]);
        });
        NSLog(@"全部执行结束");
        dispatch_async(dispatch_get_main_queue(), ^{
            //在main Dispatch queue中执行处理,更新用户界面等待
            NSLog(@"done");
        });
    });

}
打印结果:
2016-11-04 17:36:02.258 ThreadDemo[29635:5723967] blk3---reading
2016-11-04 17:36:02.258 ThreadDemo[29635:5723940] blk2---reading
2016-11-04 17:36:02.258 ThreadDemo[29635:5723929] blk1---reading
2016-11-04 17:36:02.258 ThreadDemo[29635:5723968] blk---writing
2016-11-04 17:36:02.259 ThreadDemo[29635:5723940] 1====<__NSGlobalBlock__: 0x102895af0>
2016-11-04 17:36:02.259 ThreadDemo[29635:5723967] 2====<__NSGlobalBlock__: 0x102895b30>
2016-11-04 17:36:02.259 ThreadDemo[29635:5723929] 0====<__NSGlobalBlock__: 0x102895ab0>
2016-11-04 17:36:02.259 ThreadDemo[29635:5723968] 3====<__NSGlobalBlock__: 0x102895b70>
2016-11-04 17:36:02.259 ThreadDemo[29635:5723940] 全部执行结束
2016-11-04 17:36:02.259 ThreadDemo[29635:5723499] done

10. 队列的挂起和唤醒

  • 队列的挂起与唤醒相对较为简单,如果你想对一个队列中的任务的执行进行挂起,那么你就使用dispatch_suspend()函数即可。如果你要唤醒某个挂起的队列,那么你就可以使用dispatch_resum()函数。这两个函数所需的参数都是你要挂起或者唤醒的队列,鉴于知识点的简单性就不做过多的赘述了。
#pragma mark -Dispatch_suspend/Dispatch_resume
- (void)GCDDispatch_suspend_resume{
    //系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
    
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", 0);
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", 0);
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_async(queue1, ^{
        for (int i = 0; i < 5; i++) {
            NSLog(@"%@-------%d",[NSThread currentThread],i);
            sleep(1);         
        }
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"task2");
    });
    
    dispatch_group_async(group, queue1, ^{
        NSLog(@"task1 finished!");
    });
    dispatch_group_async(group, queue2, ^{
       dispatch_suspend(queue1);//挂起
       NSLog(@"task2 finished!挂起queue1");
       [NSThread sleepForTimeInterval:20.0];
       dispatch_resume(queue1);//唤醒队列
        
    });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    dispatch_async(queue1, ^{
        NSLog(@"task3");
    });
    dispatch_async(queue2, ^{
        NSLog(@"task4");
    });
}
打印结果:可以先思考一下打印结果
2016-11-04 17:59:41.219 ThreadDemo[29749:5738141] task2
2016-11-04 17:59:41.219 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------0
2016-11-04 17:59:41.220 ThreadDemo[29749:5738141] task2 finished!挂起queue1
2016-11-04 17:59:42.220 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------1
2016-11-04 17:59:43.223 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------2
2016-11-04 17:59:44.225 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------3
2016-11-04 17:59:45.230 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------4
2016-11-04 18:00:01.220 ThreadDemo[29749:5738141] task1 finished!
2016-11-04 18:00:01.220 ThreadDemo[29749:5738141] task3
2016-11-04 18:00:01.221 ThreadDemo[29749:5738769] task4

11. 信号量Dispatch Semaphore

当并行执行的处理更新数据时,会产出数据不一致的情况,有时应用就会异常退出。可以使用Serial Dispatch Queue 和 dispatch_barrier也可以处理这种情况,我们也可以使用信号量Dispatch Semaphor来更好的处理线程安全和同步的问题。

Dispatch Semaphore是持有计数的信号。使用计数来实现该等待还是运行功能。计数为0时等待,计数为1或者大于1时,减去1而不是等待,就可以运行。

其实信号量就是根据pv操作来实现的

  • p操作-1 v操作+1

信号量就是一个资源计数器,对信号量有两个操作来达到互斥,分别是P和V操作。

一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程1运行时,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这是信号量值为0。系统中规定当信号量值为0是,必须等待,直到信号量值不为零才能继续操作。 这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就到到了进程1排他访问。 当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这是信号量的值变为1. 这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0.次数进程2咱有资源,排他访问资源。 这就是信号量来控制互斥的原理

信号量为0则阻塞线程,大于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步。

- (void)GCDDispatchSemaphore{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
  //创建一个信号量dispatch
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    NSMutableArray *array = [NSMutableArray new];
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            //一直等待信号量大于0
            dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_FOREVER));
            
          //进行排他控制的处理代码...
          //将dispatch semphore的计数器减1 
            [array addObject:[NSNumber numberWithInteger:i]];
              
            //排他控制处理结束
            //将dispatch semphore的计数器加1    
            //如果有通过dispatch_semaphore_wait函数dispatch semphore的计数值增加的线程,等待就由最先等待的线程执行                  
            dispatch_semaphore_signal(semaphore);
        });
        
        NSLog(@"%@",array);
    }
}
  • 创建一个信号量dispatch dispatch_semaphore_t dispatch_semaphore_create(long value);
  • 等待信号量longdispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);返回值和dispatch_group_wait函数相同,也可以通过下面进行分支处理:
long  result = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10*NSEC_PER_SEC)));
            
 if (result == 0){
      //进行排他控制的处理代码...
      //将dispatch semphore的计数器减1 
      [array addObject:[NSNumber numberWithInteger:i]];
              
      //排他控制处理结束
      //将dispatch semphore的计数器加1    
      //如果有通过dispatch_semaphore_wait函数dispatch semphore的计数值增加的线程,等待就由最先等待的线程执行                  
      dispatch_semaphore_signal(semaphore);
}else{
      //这个时候计数值为0,在达到指定时间为止还是等待状态。
        //处理逻辑....
}

  • 在进行排他处理结束时,通过dispatch_semaphore_signal来讲计数值加1

GCD总结

  1. 开不开线程由执行任务的函数决定
  • 异步开,异步是多线程的代名词
  • 同步不开
  1. 开几条线程由队列决定
  • 串行队列开一条线程(GCD会开一条,NSOperation Queue最大并发数为1时也可能开多条)
  • 并发队列开多条线程,具体能开的线程数量由底层线程池决定
  1. GCD的使用步骤
  • 1.创建block任务
  • 2.创建GCD队列(串行和并发)
  • 3.把block任务放到GCD队列里,以异步或者同步方式执行GCD队列

结尾:

能看到这里的小伙伴都是真爱啊,内容太多了。学完这个你们多思考一下里面方法的联系以及实现,好了今天的GCD就讲到这里,中间有什么错误欢迎你们批评指出!下一篇博客将带你走进NSOperation。

喜欢的话就请点个喜欢加个关注_(楼主好无耻啊)❤️

ThreadDemo下载链接

参考书籍:Objective-C高级编程iOS与OS X多线程和内存管理

推荐阅读更多精彩内容

  • Dispatch Queues dispatch queues是执行任务的强大工具,允许你同步或异步地执行任意代码...
    YangPu阅读 174评论 0 4
  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 301评论 0 3
  • 欢迎大家指出文章中需要改正或者需要补充的地方,我会及时更新,非常感谢。 一. 多线程基础 1. 进程 进程是指在系...
    xx_cc阅读 5,978评论 13 67
  • 原创内容,转载请注明出处: http://www.jianshu.com/p/ac11fe7ef78c 前言 多线...
    抱紧我的小鲤鱼阅读 7,239评论 7 79
  • 从哪说起呢? 单纯讲多线程编程真的不知道从哪下嘴。。 不如我直接引用一个最简单的问题,以这个作为切入点好了 在ma...
    Mr_Baymax阅读 1,548评论 1 17