[置顶] GCD深入学习之GCD的初识

标签: gcd
5381人阅读 评论(0) 收藏 举报
分类:

如果移动端访问不佳,可以访问我的个人博客

现在网上关于GCD的介绍已经很多了,在项目中也经常用到,但是没怎么深入研究过,打算写一系列关于GCD使用,参考其他大神写的博客和Apple的技术文档总结一下,一是自己深入学习一下,二是以后忘了可以回过头来温习一下~

什么是GCD?

GCD全名是Grand Central Dispatch(大中央调度器),是系统级的,存在于libdispatch.dylib这个库里,是Apple开发的一个多核编程的解决方法,它提供了一下几种好处:

  • GCD用纯C编写,可以提高应用程序的响应能力,更加高效;
  • GCD使用简单,会自动利用更多的CPU内核(比如双核、四核),自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码,提供更容易并发模型,有助于避免并发错误。

GCD的相关术语

要了解GCD,你必须熟悉相关的线程和并发的几个概念。这些既可以是模糊的,微妙的,所以花点时间在GCD的背景下简要回顾一下他们。

串行和并发

在执行任务时,串行是任务顺序执行,执行完一个下一个。并发就是任务可能同时执行多个任务。在GCD中一个任务就是一个闭包,这比NSOperation中的任务更加容易理解。

同步和异步

在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

临界区

通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资 源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。

死锁

是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,简单来说就是A线程在等待B线程完成后执行,B线程也在等待A线程完成后执行,这样就出现了死锁的现象。

线程安全

线程安全的代码可以从多个线程或并发任务安全地调用,而不会造成任何问题(数据损坏,系统崩溃等)。例如当你多线程编程时,你用let定义一个数组,因为它是只读的,你能在同一时间不同线程去使用它,而不会造成线程安全的问题,然而当你用var定义一个数组时就不一样了,它不是线程安全的,当多个线程在同一时间访问和修改数组时会产生不可预知的结果。

上下文切换

上下文切换是存储和恢复执行的状态,是你在一个进程中执行不同的线程之间切换的过程。这个过程是编写多任务处理应用程序时很常见,但也带来了一些额外的开销成本。就像并发就是通过切换上下文来实现的。

并发和并行

在多核设备上执行任务时,每个CPU可以单独工作,每个CPU同时执行不同的任务,这就是并行,然而为了使单核的设备实现这种效果达到类似的效果,因为它们只有一个线程,它们只能通过快速的上下文切换来达到并行的假象,这就是并发,如下图所示:

并发

GCD队列

GCD用dispatch queues来处理提交的任务,队列用FIFO(先进先出)的原理来管理这些任务,所有的dispatch queues本身是线程安全的,你可以从多个线程去访问它们,在GCD 中提供了两种队列,分别是串行队列和并发队列。

串行队列

串行队列保证同一时间队列里只有一个任务在执行,只有等待第一个任务执行完成后才会执行下一个任务,你也不知道两个任务之间的间隔时间是多少,如下图所示:

串号队列

使用串行队列有一下的优点:

  1. 能确保对一个共享资源进行串行化的访问,避免了数据竞争;
  2. 任务的执行顺序是可预知的,你向一个串行队列提交任务时,它们被执行的顺 序与它们被提交的顺序相同;

并发队列

并发队列可以让你并行的执行多个任务。任务按照它们被加入到队列中的顺序依次开始,但是它们都是并发的被执行,并不需要彼此等待才开始。并发队列能保证任务按同一顺序开始,但你不能知道执行的顺序、执行的时间以及在某一时刻正在被执行任务的数量,具体如下图所示:

并发队列

GCD 的队列类型

在使用过程中系统会自动给每个应用提供一个串行队列和四个并发队列,其中串行队列为全局可用的串行队列,在应用的主线程中执行任务且只有一个,这个队列被用来更新 App 的 UI,执行所有与更新 UIViews 相关的任务。该队列中同一时刻只执行一个任务,这就是为什么当你在主队列中运行一个繁重的任务时UI会被阻塞的原因。GCD提供了一下三种队列:

  • 主队列:任务以串行的方式执行在您的应用程序的主线程;
  • 并发队列:任务在先进先出的顺序列中移除,但并发运行,可以按照任何顺序完成;
  • 串行队列:以先进先出顺序执行一次任务。

除主队列之外,系统还提供了4个并发队列。我们管它们叫 Global Dispatch queues(全局派发队列)。这些队列对整个应用来说是全局可用的,彼此只有优先级高低的区别。要使用其中一个全局并发队列的话,你得使用 dispatch_get_global_queue 函数获得一个你想要的队列的引用,该函数的第一个参数取如下值:

  • DISPATCH_QUEUE_PRIORITY_HIGH:高优先级的队列,高于其他任务优先级的队列。

  • DISPATCH_QUEUE_PRIORITY_DEFAULT:默认优先级队列,高于下面两个优先级,

  • DISPATCH_QUEUE_PRIORITY_LOW:低优先级队列,低于上面两个优先级。
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND:最低的优先级,使用它可以尽可能的减少对系统的影响。

你可以创建任何数量的串行或并发队列。使用并发队列的情况下,即使你可以自己创建,但是还是强烈建议你使用上面那四个全局队列,避免带来没必要的麻烦。

创建和管理GCD队列

dispatch_get_main_queue() -> dispatch_queue_t!

返回值

通过这个函数来获取主线程队列:

//返回主线程队列
dispatch_get_main_queue()

dispatch_get_global_queue(identifier: Int, _ flags: UInt) -> dispatch_queue_t!

返回值

通过这个函数来获取系统提供的4个并发队列

参数列表

参数名 参数描述
identifier 代表的队列的优先级,为DISPATCH_QUEUE_PRIORITY_HIGH、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND中的一个
flags 苹果官方解释是保留已提供将来使用,目前让这个参数为0
//返回DISPATCH_QUEUE_PRIORITY_HIGH优先级的队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
//返回DISPATCH_QUEUE_PRIORITY_DEFAULT优先级的队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
//返回DISPATCH_QUEUE_PRIORITY_LOW优先级的队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
//返回DISPATCH_QUEUE_PRIORITY_BACKGROUND优先级的队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)

dispatch_queue_create(label: UnsafePointer, _ attr: dispatch_queue_attr_t!) -> dispatch_queue_t!

返回值

通过这函数来创建一个新的队列。

参数列表

参数 参数描述
label 为你创建的队列的标签,建议用反向DNS的命名风格(com.example.myqueue),这个参数是可选的,可以为NULL。
attr GCD队列的属性,这个参数来选择创建的是串行队列(DISPATCH_QUEUE_SERIAL)还是并发队列(DISPATCH_QUEUE_CONCURRENT)和队列的优先级别。
//创建一个标识为com.wcl.www的并发队列
dispatch_queue_create("com.wcl.www", DISPATCH_QUEUE_CONCURRENT)
//创建一个标识为com.imwcl.www的串行队列
dispatch_queue_create("com.imwcl.www", DISPATCH_QUEUE_SERIAL)

dispatch_get_current_queue()

返回值

返回当前任务中的队列。

dispatch_queue_attr_make_with_qos_class(attr: dispatch_queue_attr_t!, _ qos_class: dispatch_qos_class_t, _ relative_priority: Int32) -> dispatch_queue_attr_t!

返回值

返回一个适用于创建一个想要的服务质量信息的GCD队列的属性。主要用于dispatch_queue_create函数。

参数列表

参数 参数描述
attr GCD队列的属性,这个参数来选择创建的是串行队列(DISPATCH_QUEUE_SERIAL)还是并发队列(DISPATCH_QUEUE_CONCURRENT)和队列的优先级别。
qos_class 这个参数为队列优先级,同样为DISPATCH_QUEUE_PRIORITY_HIGH、DISPATCH_QUEUE_PRIORITY_DEFAULT、DISPATCH_QUEUE_PRIORITY_LOW、DISPATCH_QUEUE_PRIORITY_BACKGROUND中的一个。
relative_priority 这个参数为QOS类中相对优先级,这个值必须小于0,大于QOS_MIN_RELATIVE_PRIORITY,根据log数据发现QOS_MIN_RELATIVE_PRIORITY的值为-15,那说明第二个参数的值在0~-15之间。

第三个参数为QOS类中相对优先级,也就是第二个参数的类,这个值必须小于0,大于QOS_MIN_RELATIVE_PRIORITY

global queue优先级映射到以下quality-of-service类:

  • DISPATCH_QUEUE_PRIORITY_HIG映射到QOS_CLASS_USER_INITIATED
  • DISPATCH_QUEUE_PRIORITY_DEFAULT映射到QOS_CLASS_DEFAULT
  • DISPATCH_QUEUE_PRIORITY_LOW映射到QOS_CLASS_UTILITY
  • DISPATCH_QUEUE_PRIORITY_BACKGROUND映射到QOS_CLASS_BACKGROUND
let att = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, QOS_MIN_RELATIVE_PRIORITY)
//根据att来创建一个队列
dispatch_queue_create("com.wcl.www", att)

dispatch_queue_get_label(dispatch_queue_t queue)

返回值

返回已经创建队列的指定标签。如果队列在创建过程中没有提供标签,则可能返回NULL。

//返回队列的标签
dispatch_queue_get_label(dispatch_get_main_queue())

dispatch_main( void)

执行主队列上被提交的所有block。这个函数是为主线程而存在的并且等待执行提交到主队列中的block。在主线程中调用了UIApplicationMain(iOS) ,NSApplicationMain(OS X),或者CFrunLoopRun的应用程序一定不要调用dispatch_main。

int main(int argc, const charchar * argv[])  
{  

    @autoreleasepool {  
        dispatch_async(dispatch_get_main_queue(), ^{  
            NSLog(@"等待1。。。。");  
        });  
        dispatch_async(dispatch_get_main_queue(), ^{  
            NSLog(@"等待1。。。。");  
        });  
        dispatch_main();  
    }  
    return 0;  
} 

如上:如果不调用dispatch_main()函数,则不会打印出结果。

dispatch_set_target_queue(object: dispatch_object_t!, _ queue: dispatch_queue_t!)

参数列表

参数 参数描述
object 要修改的对象,该参数不能为空。
queue 处理这个对象的目标队列,这个参数不能为NULL

给GCD对象设置目标队列,这个目标队列负责处理这个对象。处理的对象分别有一下几种:

  • GCD队列:dispatch_queue_t,一个GCD队列的优先级是继承自它的目标队列的。使用dispatch_get_global_queue函数去获得一个合适的目标队列,这个目标队列就是你所需的优先级。

    如果你提交一个block到一个串行队列中,并且这个串行队列的目标队列是一个不同的串行队列,那么这个block将不会与其他被提交到这个目标队列的block或者任何其他有相同目标队列的队列同时调用。

  • GCD数据源:dispatch_source_t,为一个GCD数据源的目标队列指定了它的事件处理者的block和取消事件处理的block。

  • GCD I/O通道:dispatch_io_t,一个GCD I/O通道的目标队列指定了被执行的I/O操作。这可能会影响I/O操作结果的优先级。例如,如果这个通道的目标队列的优先级被设置为DISPATCH_QUEUE_PRIORITY_BACKGROUND,那么当有I/O操作争夺的时候,任何在这个队列上通过dispatch_io_read或dispatch_io_write执行的I/O操作都会被压制。

关于dispatch_source_tdispatch_io_t的用法会在以后的章节去介绍。

总结:这篇文章主要详细的介绍了一下关于多线程的知识和GCD队列的的创建,关于GCD的用法和其他方面的知识会在后续文章里面去描述,给自己立一个flag,争取完全弄懂以后写出好的博客来。

参考文档

苹果官方GCD参考文档

国外一篇好的关于GCD的文档

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:233328次
    • 积分:2959
    • 等级:
    • 排名:第11947名
    • 原创:67篇
    • 转载:8篇
    • 译文:0篇
    • 评论:161条
    GitHub
    博客专栏
    最新评论