GCD --换种简单的方式理解

作者: 李周 | 来源:发表于2017-11-26 15:08 被阅读160次

    在开发的过程中,对于多线程的需求越来越高,但是由于在概念的理解上一直都有些许的模糊不清,所以这次也趁着重读一遍高级编程中的GCD部分,将多线程重新整理了一下。


    1. 多线程的理解
    2. GCD方法的理解

    1. 多线程的理解

    多线程的本义很好理解:就是为了多个不同的任务开启多个线程,让主线程能及时的响应用户的操作。但是对于和多线程紧紧相关的异步、同步和并行、串行而言,很容易搞模糊。所以下面还是按照自己的思路一步步进行分析:

    1)队列

    官方点的理解:队列就是任务的集合,采用的是先进先出的原则,每执行完一个任务就会释放一个任务。通白点理解就是:对于每一个新任务而言,先排到队伍的末尾,具体该如何执行等按序轮到才知道。

    2)同步与异步

    ① 同步:在当前线程中执行任务,并没有开启新线程的能力
    ② 异步:具有开启新线程的能力,能在新的线程中执行任务

    首先,通过图例解释异步执行的过程:


    异步执行示范

    异步执行并不会堵塞当前的线程,只是告诉当前的线程:

    你只要帮我将任务加入queue队列中即可,任务该如何执行无你无关。

    所以从上面图例中的主线程视角看整个的执行过程,就是:


    异步执行时主线程视角

    主线程并不会直接看到block中关于任务的内容,只是将dispatch_async和NSLog都当做一行执行命令来执行。也就是说:主线程只管执行各个命令行,而队列中加入的任务在其他线程中执行:


    异步执行打印结果

    同步执行正好和异步执行相反,以下面的图例分析:


    同步执行示范
    不仅需要将任务加入到queue队列中,还会强制要求当前线程暂停,来执行该队列中的任务。

    所以结果和执行的顺序一致:


    同步执行的打印结果
    3)队列类型:串行和并行

    ① 串行 --->一个任务执行完再执行下一个任务,按序一个个执行。

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    

    ① 并行 --->不等待当前任务执行完就执行下一个任务。

    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    

    同步还是异步、串行还是并行对于任务而言就相当于两个同等重要的原则。用商店的收银台为例来理解:

    一家商店的收银处,同步表示只有一个收银台,而异步表示有多个收银台。而串行表示管你有几个
    收银台,所有人就排成一队;而并行表示尽量分散,有几个排几个。
    

    在对多线程的处理过程中,同步执行其实是很容易出错的地方。个人理解就是:在当前线程正在处理“将任务加入queue队列”中时,同步执行会强制性的要求当前线程暂停手中的任务,从而用所有的资源来执行"queue队列中的任务"。举例而言:


    同步执行易报错点

    主线程正在执行任务时,强制要求执行block中的新任务。


    同步执行易报错点

    异步执行创建的线程正在执行任务,强制要求执行同步中的任务。

    所以在对同步执行的使用中,要特别注意不要进入到崩溃的逻辑中。

    2. GCD方法的理解
    dispatch_after
    dispatch_after示例

    对于该方法而言,有两个地方需要特别注意:
    ① NSEC_PER_SEC
    dispatch_afer方法中的第二个参数的时间单位并不是秒而是"纳秒",所以这也是为什么需要使用系统定义的变量进行转换的原因。系统提供的装换方式:

    每秒有多少纳秒
    #define NSEC_PER_SEC 1000000000ull 
    每微妙有过少毫秒
    #define NSEC_PER_MSEC 1000000ull
    每秒有多少毫秒
    #define USEC_PER_SEC 1000000ull
    每毫秒有多少纳秒
    #define NSEC_PER_USEC 1000ull
    

    ②4秒之后执行?
    dispatch_after并不是在指定时间后执行处理,而只是在指定时间内将任务追加到Queue队列中。
    对于上面的图例而言,该队列是在主线程的RunLoop中执行,而主线程的RunLoop每隔1/60秒执行,而该Block中的任务要求延迟4秒之后执行,所以最快在4秒 +1/60后执行。并且由于主队列中有大量的处理或主线程本身就有一定的延迟,所以执行的时间会更长。

    所以,dispatch_afer只适用于一些想大致延迟执行的处理中,并不适用于对时间有严格要求的情况下。
    dispatch_barrier_async

    对于多线程而言,最需要关注的就是数据竞争的问题。虽然串行执行能避免该问题,但是现实生活中数据的读取往往是要求能并行执行的,只有当遇到对数据的写入时才需要额外的关注。
    举例而言:


    并行执行读取和写入操作

    很显然,读取和写入并行的情况下,读取的数据会出现错误:


    简单的并行执行数据读取出错

    而dispatch_barrier_async的定义就是:
    等待之前的并行执行全部结束之后,再将指定的处理追加到队列中。等待该函数追加的处理执行完毕之后,队列才会恢复到一般的动作:


    执行结果
    所以,dispatch_barrier_async就是等待之前的并行操作执行之后,再单独的执行该函数中的任务处理,完毕之后就是普通的执行。
    Dispatch Semaphore

    在实际的开发过程中,经常会出现一种需求:在同一个界面中需要多次请求,在所有请求结束之后再刷新界面。在此之前,我一直都很low的用在一个请求结束时嵌入另一个请求的方法,直到认识到该方法:


    信号量的使用

    上面的图例有几个需要知道的地方:
    ①dispatch_semaphore_t
    Dispatch Semaphore是持有计数的信号,计数为0时等待,计数为1或大于1时,减去1而继续执行。

    首先,创建信号时赋值计数的初始值为0

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    

    其次,当信号量遇到wait方法时,判断计数是否大于等于1。如果等于0,进入等待状态,所以等价于该线程暂停在该处。所以:

    [task resume]必须写在wait方法之前

    最后,当任务执行结束,signal函数会将计数值加1,此时wait函数会识别到计数不为0,会减1继续向下执行。

    dispatch_semaphore_signal(semaphore);
    

    信号量配合group使用,当所有的请求结束时,通知主线程进行界面的刷新:


    通知主线程进行界面刷新

    对于类似多个网络请求的情况,GCD还提供了另外的一种方法:


    多个网络请求第二种方法
    dispatch_group_enter(group);
    dispatch_group_leave(group);
    

    关于越底层的东西,越值得深入的思考。GCD的理解也算是基本的入门,也想要更多的去了解这些东西。冬天到了,得好好学习了。

    相关文章

      网友评论

        本文标题:GCD --换种简单的方式理解

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