GCD

作者: 彼岸的黑色曼陀罗 | 来源:发表于2016-09-25 19:55 被阅读0次

    GCD

    GCD简介

    • Grand Central Dispatch中枢调度器
    • 纯C语言的,提供了非常强大的函数
    • 优势
      • GCD是苹果公司为多核的并行运算提出的解决方案
      • GCD会自动利用更多的CPU内核(比如双核、四核),充分利用设备的多核
      • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
      • github上面有源码

    任务和队列

    • GCD中的核心概念
    • 任务:执行什么操作,需要用函数封装起来(同步函数|异步函数)
    • 队列:用来存放任务的
    • 使用步骤
      • 定制任务
        • 确定想要做的事情
      • 将任务添加到队列中
        • GCD会自动将队列中的任务取出,放到对应的线程中执行
        • 任务的取出遵循队列的FIFO原则:先进先出,后进后出
    • 执行任务
      • 同步
        • dispatch_sync(dispatch_queue_t queue,dispatch_block_t block)
        • 只能在当前线程中执行任务,不具备开启新线程的能力
      • 异步
        • dispatch_asyn
        • 可以在新的线程中执行任务,具备开启新线程的能力
    • 队列的类型
      • 并发队列
        • 可以让多个任务并发执行
        • 并发功能只有在异步函数下才有效
      • 串行队列
        • 让任务一个接着一个的执行
    • 容易混淆的术语
      • 同步异步:能不能开启新的线程
        • 同步:只是在当前线程中执行任务,不具备开启新线程的能力
        • 异步:可以在新的线程中执行任务,具备开启新线程的能力
      • 并发|串行:任务的执行方式
        • 并发:允许多个任务并发执行
        • 串行:一个任务执行完毕之后,再执行下一个任务

    GCD基本使用

    • 四种组合方式

    • 异步函数+并发队列

      • 获得队列
        • dispatch_queue_t 结构体
        • dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_CONCURRENT)创建
          • 第一个参数:C语言的字符串,给队列起一个名字(推荐写法:公司域名倒过来写,后面跟上作用)名字的作用:是用来调试的
          • 第二个参数:队列的类型,传一个宏
            • DISPATCH_QUEUE_CONCURRENT
            • DISPATCH_QUEUE_SERIAL
      • 封装任务并且把任务添加到队列中
        • dispatch_async(queue,任务)
          • 第一个参数:队列
          • 第二个参数:block可以封装一个任务
        • 封装任务
        • 把任务添加到队列中
      • 会开多条线程,所有的任务是并发执行的
      • 注意:使用GCD的时候,具体开几条线程,并不是由任务的数量来决定的,是由系统自动决定
        • 会看CPU的处理情况,线程是可以复用的
    • 异步函数+串行队列

      • 获得队列
        • dispatch_queue_t 结构体
        • dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_SERIAL)创建
          • 第一个参数:C语言的字符串,给队列起一个名字(推荐写法:公司域名倒过来写,后面跟上作用)
          • 第二个参数:队列的类型,传一个宏
            • DISPATCH_QUEUE_CONCURRENT
            • DISPATCH_QUEUE_SERIAL
      • 封装任务并且把任务添加到队列中
        • dispatch_async(queue,任务)
          • 第一个参数:队列
          • 第二个参数:block可以封装一个任务
        • 封装任务
        • 把任务添加到队列中
      • 会开启一条子线程,所有的任务都是串行执行的,需要等前一个任务执行完,后面的任务才会执行
    • 同步函数+并行队列

      • 获得队列
        • dispatch_queue_t 结构体
        • dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_CONCURRENT)创建
          • 第一个参数:C语言的字符串,给队列起一个名字(推荐写法:公司域名倒过来写,后面跟上作用)
          • 第二个参数:队列的类型,传一个宏
            • DISPATCH_QUEUE_CONCURRENT
            • DISPATCH_QUEUE_SERIAL
      • 封装任务并且把任务添加到队列中
        • dispatch_sync(queue,任务)
          • 第一个参数:队列
          • 第二个参数:block可以封装一个任务
        • 封装任务
        • 把任务添加到队列中
      • 不会开启新的线程,所有的任务都在当前线程中串行执行
    • 同步函数+串行队列

      • 获得队列
        • dispatch_queue_t 结构体
        • dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_SERIALRENT)创建
          • 第一个参数:C语言的字符串,给队列起一个名字(推荐写法:公司域名倒过来写,后面跟上作用)
          • 第二个参数:队列的类型,传一个宏
            • DISPATCH_QUEUE_CONCURRENT
            • DISPATCH_QUEUE_SERIAL
      • 封装任务并且把任务添加到队列中
        • dispatch_sync(queue,任务)
          • 第一个参数:队列
          • 第二个参数:block可以封装一个任务
        • 封装任务
        • 把任务添加到队列中
      • 不会开启新的线程,所有的任务都在当前线程中串行执行
    • 同步函数不管是串行还是并发都是串行执行任务的。

    全局并发队列

    • 并发队列

      • 队列中的任务可以同时执行
      • 并发队列分为两种
        • 自己创建的并发队列dispatch_queue_create
        • 全局并发队列(默认存在)
    • GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建

    • dispatch_get_global_queue函数获得全局的并发队列

      • 参数一:队列的优先级dispatch_queue_priority
        • DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级 == 0 ,不要传其他的,会引发线程安全问题(反转)
      • 参数二:暂时无用,用0即可,给未来使用的
    • 注意:线程的数量并不是由任务的数量决定的(是由系统决定的)

    主队列

    • 串行队列
      • 必须一个接着一个的执行,要等当前任务执行完毕之后,才能执行后面的任务
      • 串行队列有两种
        • 自己创建的串行队列dispatch_queue_create("com.520it,www.download",DISPATCH_QUEUE_SERIAL)
          • DISPATCH_QUEUE_SERIAL == NULL
        • 主队列(和主线程相关)
          • 主队列是GCD自带的一种特殊的串行队列
          • 放在主队列中的任务,都会放到主线程中执行 | 调度的时候比较特殊!
          • dispatch_get_main_queue()获得主队列
    • 特点:
      • 凡是放在主队列中的任务都必须要在主线程中执行
      • 本身是一个串行队列,串行队列的特点它都有
      • 如果主队列中有任务需要执行,那么主队列会安排主线程来执行当前队列中的任务,但是在调度之前,会先检查主线程的状态,如果主线程空闲,如果主线程在忙,就暂停调度队列中的任务,等到主线程空闲的时候再调度。
    • 主队列+异步函数
      • 获得主队列
        • dispatch_get_main_queue()
      • 添加任务到队列中
        • dispatch_async()
      • 不会开线程,所有的任务都在主线程中串行执行
    • 主队列+同步函数
      • 获得主队列
        • dispatch_get_main_queue()
      • 添加任务到队列中
        • dispatch_sync()
      • 任务并没有执行,有一个死锁
        • 主队列里的任务必须要在主线程中执行
        • 主线程在执行同步函数
        • 主队列工作模式:如果发现队列中有任务,那么就安排主线程来执行,但是在安排之前, 会先检查主线程的状态(看主线程是否空闲),如果主线程在忙,那么就暂停调度,直到主线程空闲
      • 如果在子线程中调度就不会死锁
        • performSelectorInBackground:@selector(主队列+同步函数) withObject:
        • 不会发生死锁了,为什么?
          • 主线程空闲,没有事情做,主线程就执行了任务
    • 开发中,常用的是:异步函数+并发队列|串行队列,面试中会把代码给你,问你打印的结果是什么

    • 问题:同步函数+主队列会死锁?异步函数+主队列不会死锁?

      • 同步异步:
        • 是否具有开线程的能力
        • 同步函数同步执行任务,异步函数异步执行任务
        • 同步执行:
          • 必须要等当前任务执行完,才能执行后面的任务,如果我没有执行,那么我后面的也别想执行
        • 异步执行:
          • 我开始执行后,可以不用等我执行完,就执行后面的任务
        • 异步函数+主队列不会发生死锁
          • 获得一个主队列
          • 同步函数
            • 封装一个任务
            • 把任务添加到队列中去,检查主线程状态(较忙)死锁
          • 异步函数
            • 队列中有任务,要求主线程去执行
            • 异步函数异步执行,跳过任务,把任务存在队列中
            • 代码执行完毕后,主线程空闲了,就回来再执行队列里的任务,就不会发生死锁
    • 异步函数 +并发队列 ps 同步函数+并发队列
      • 1.start - end - 任务 - 异步
      • 2.start - 任务 - end - 同步
    • 问题:同步函数+串行队列不会发生死锁?

      • 串行队列,在当前线程中执行,串行队列有任务就会执行,不会检查主线程的状态, 不管线程是否在忙,都会强行让线程来执行任务
    • 总结

      • 异步函数+不是主队列就一定会开线程
      • 同步+并发:不会线程,串行执行任务
      • 同步+串行:不会开线程,串行执行任务
      • 同步+主队列:不会开线程,死锁
      • 异步+并发:开多条线程,并发执行任务
      • 异步+串行:开线程,串行执行任务
      • 异步+主队列:没有开启新线程,串行执行任务

    线程间通信

    • 开子线程下载图片
      • 异步+非主队列
      • 获得全局并发队列
        • dispatch_get_global_queue(0,0)
      • 使用异步函数+并发队列
        • dispatch_async(queue,block)
          • 确定URL
          • 把图片的二进制数据下载到本地
            • dataWithContentsOfURL:
          • 转化格式
            • imageWithData:
          • 显示图片
            • 回到主线程刷新UI
            • GCD可以嵌套
            • dispatch_async(dispatch_get_main_queue(),^{
              self.imageV.image = image;
              })
      • 配置info.plist文件
    • 问:回到主线程,是异步函数+主队列,在这里可以用同步函数+主队列吗?
      • 可以,当前执行的是子线程,不会发生死锁

    常用函数

    • 开发中比较常用的GCD函数
    延迟执行
    • 一段时间之后再执行任务
    • 三种方法可以实现延迟执行
      • 方法一:performSelector:withObject:afterDelay:
      • 方法二:定时器NSTimer scheduledTimeWithTimeInterval:target:selector:userInfo:repeats:
      • 方法三:GCD dispatch_after(dispatch_time(DISPATCH_TIME_NOW,(int64_t)(3.0*NSEC_PER_SEC)),dispatch_get_main_queue),^{}
        • 默认是主队列 ,可以改为全局并发队列
        • 队列决定代码块在哪个线程中调用
        • 主队列 - 主线程
        • 并发队列 - 子线程
        • 好处:可以控制在哪个线程里面执行任务
    • 延迟执行内部实现原理?
      • A:先把任务提交到队列,然后三秒之后再执行该任务(错误)
      • B:先等三秒的时间,然后再把任务提交到队列
      • 正确答案:B
      • 延迟执行就是延迟提交
    一次性代码
    • 只会执行一次,一辈子只会执行一次
    • dispatch_once(&onceToken,^{});
    • 特点:
      • 整个程序运行过程中,只会执行一次
      • 它本身是线程安全的,不用考虑线程安全的问题
    • 怎么做到永远只会执行一次呢?
      • 通过静态变量的值来判断的
        • static dispatch_once_t onceToken;
      • 判断onceToken值是否等于0,不等于零就不执行了
    快速迭代
    • 迭代 - 递归 - 遍历 - 博客ios开发中实用技巧

    • 遍历

      • for循环,开多条线程就可以提高效率
      • GCD的快速迭代dispatch_apply(10,并发队列,^(size_t index){})
        • 参数一:遍历多少次,size_t 类似NSInteger类型
        • 参数二:对列,看到队列就要想,block块在哪个线程中调用
        • 参数三:^(size_t ){}类似于for循环里面的i
    • for循环与GCD的快速迭代区别

      • 快速迭代没有顺序,开始迭代的时候是按照顺序开始的,谁先结束是不知道的,所以打印出的结果是没有顺序的,是并发执行的
      • GCD快速迭代:会开启多条子线程和主线程一起并发的执行任务
      • 注意:队列怎么传?不能传主队列!不传串行队列,不会死锁,不会开线程,执行方式和for循环一样,串行执行的!传并发队列!!!
      • 用快速迭代就用并发队列!!!!
    • 快速迭代的应用

      • 剪切文件
        • 获得路径下面的子路径(打印的是文件的名称,剪切的时候需要的是文件的全路径)
          文件管理者NSFileManager defaultManager]subpathsAtPath:
        • 遍历数组(快速迭代)
    dispatch_get_global_queue(0,0)
    dispatch_apply(subpaths.count,queue,^(size_t){
                    获得文件的名称
                    获得要剪切文件的全路径(stringByAppendingPathComponent:该方法在拼接的时候会自动添加一个/)
                    拼接文件的目标路径(toFullPath)
                    剪切文件
                    [NSFileManager defaultManager] moveItemAtpath:toPath:error:(第一个参数:要剪切的文件在哪里?第二参数:要剪切到哪里去,全路径;第三个参数:)
                    }) 
    
    栅栏函数
    • 获得队列:并发
      • dispatch_queue_create("",DISPATCH_QUEUE_CONCURRENT)
      • 为什么不用全局并发队列?
        • 栅栏函数不能使用全局并发队列
        • 如果使用全局并发队列,栅栏函数就不具备拦截功能,和普通的异步函数没有任何区别
        • 使用栅栏函数,必须要自己创建队列
    • 异步函数
      • dispatch_async(queue,^{})
    • 需求:真实开发中,任务和任务之间有依赖关系,有先后顺序关系,要求执行123三个任务后,执行打印操作,再执行后面的456三个任务
    • 栅栏函数
      • dispatch_barrier_async()一般用异步函数
      • 在栅栏函数内部执行打印操作
      • 作用:
        • 等之前的所有任务都执行完毕之后,执行栅栏函数中的任务,等我的任务执行完毕之后,在执行后面的任务
        • 前面的和后面的任务都是并发执行的
    GCD队列组(掌握)
    • 和栅栏函数的功能很类似

    • 1.获得队列

      • 全局并发队列dispatch_get_global_queue()
    • 异步函数dispatch_async(queue,^{})

    • 需求,在最后拦住任务,当所有的任务执行完毕之后做一些事情!

    • 队列组(调度组)

    • 2.创建队列组dispatch_group_create()

      • 监听队列里面所有任务的执行情况
    • 3.异步函数dispatch_group_async()

      • 第一个参数:队列组
      • 第二个参数:队列
      • 通过队列组建立起队列组和队列以及任务之间的关系
    • 4.拦截dispatch_group_notify(group,queue,^{打印操作})当队列组中所有的任务都执行完毕之后,会调用该方法

    • 队列组是如何知道任务什么时候结束的呢?

      • 以前是把dispatch_group_async(group,queue,^{})拆分成三个方法
        • 1.创建队列组
        • 2.获得对列
        • 3.该方法后面的异步任务会被队列组监听dispatch_group_enter(group)
        • 4.使用异步函数封装任务
        • dispatch_async(queue,^{任务1,在里面监听,enter和leave配对使用dispatch_group_leave(group)})
        • 5.拦截通知,队列决定block块在哪个线程中执行 dispatch_group_notify(group,queue,^{})
    • dispatch_group_async()和dispatch_async()的区别

      • 相同点:
        • 封装任务
        • 把任务添加到队列
      • 不同点
        • 队列组能够监听该任务的执行情况(开始|结束)
    • 队列组的应用

      • 应用场景:下载图片1,图片2,把两张图片合成为一张图片
        • 耗时操作,耗时操作放到子线程中执行
        • 要开2~3条子线程
        • 获得一个并发队列dispatch_get_global_queue(0,0)
        • 合成图片操作,有一个隐藏的依赖关系
          • 队列组
          • 栅栏函数
        • 创建队列组dispatch_group_creat()
        • 异步函数dispatch_group_async(group,queue^{
          URL:URLWithString
          把图片的二进制数据下载到本地dataWithContentOfURL:
          转换格式imageWithData
          定义图片的属性,强引用转换好的图片
          })
        • 合成图片
          • dispatch_group_notify(group,queue,^{
            开启图形上下文UIGraphicsBeginImageContext()
            画图1和2 drawInRect:
            根据上下文得到图片 UIGraphicsGetImageFromCurrentImageContext()
            关闭上下文UIGraphicsEndImageContext()
            显示图片(线程间通信)
            dispatch_async(dispatch_get_main_queue(),^{
            self.imageV.image = image;
            })
            })

    补充

    • GCD的其他用法

      • 使用函数的方法封装任务dispatch_async_f()
        • 第一个参数:队列
        • 第二个参数:函数要接收的参数NULL
        • 第三个参数:dispatch_function_t
          • void(*dispatch_function_t)(void *)
          • (*dispatch_function_t) -- run把第一个小括号替换成函数名称
          • 第三个参数直接传run就可以了
    • 全局并发队列与自己创建的并发队列的区别

      • 全局并发队列在整个应用程序中本身是默认存在的,并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只选择其中的一个直接拿来用,而creat函数是实打实的从头开始去创建一个队列
      • 在ios6.0之前,在GCD中凡是使用了create和retain的函数在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release.当然了,在ios6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就OK
      • 在使用栅栏函数的时候,苹果官方明确规定,栅栏函数只有在和使用create函数自己创建的并发队列一起使用的时候才有效果(没有给出具体原因)
      • 其他区别涉及到XNU内核的系统级线程编程,不一一列举
      • 参考
        • GCDAPI
        • Libdispatch版本源码

    相关文章

      网友评论

          本文标题:GCD

          本文链接:https://www.haomeiwen.com/subject/yyvfyttx.html