GCD相关

作者: 纳木错_grace | 来源:发表于2018-03-16 11:34 被阅读41次

    本文是自己写的总结GCD的Demo的结果。总结的过程中,参考了很多文章,文章底部有引用链接,在此感谢
    多图,流量慎点😂。

    • 概念介绍
    • 队列的创建
    • 任务的创建与执行
    • 实践运用
    • 其他

    概念介绍

    进程和线程
    进程(process):一个执行中程序的实例。指的是一个正在运行中的可执行文件。每个进程都拥有独立的虚拟内存空间和系统资源,包括端口权限等,而且至少包含一条主线程(或者可以包括任意数量的子线程)。当一个进程的主线程退出时,这个进程就结束了。对于iOS的app来说,一个app运行起来,就是一个进程。

    进程.png

    线程:(thread),指定是一个独立的代码执行路径,(代码执行的管道),线程是代码执行路径的最小单位。

    同步和异步
    同步和异步操作的主要区别在于是否等待操作执行完成,即是否阻塞当前线程。
    同步:在发出一个同步调用时 ,在没有得到结果之前,该调用就不返回。
    异步:在发出一个异步调用后,调用者不会立刻得到结果,该调用就返回了。
    同步和异步是针对能不能开线程。同步不能开启新的线程。异步可以。异步虽然有开启新线程的能力,但不是说只要异步调用,就开启新线程了。
    在iOS中,同步和异步即针对以下两个函数。

    • 同步函数:
     dispatch_sync(queue, ^{
            
        })
    
    • 异步函数:
     dispatch_async(queue, ^{
            
        })
    

    串行和并发
    串行和并发指的是任务(代码)的执行方式。
    串行是指多个任务时,各个任务按顺序执行,挨个儿进行,完成一个之后才能进行下一个。顺序不会乱。
    并发指的是多个任务可以同时(一个时间段内)执行。异步是并发的前提。
    在iOS中,任务的执行方式(串行或并发),由队列来管理。队列是用来存放任务的。
    队列和线程
    在iOS中,有两种不同类型的队列:串行队列(主队列是一种特殊的串行队列)和并发队列。串行队列一次只能执行一个任务,并发队列则可以允许多个任务同时执行。iOS系统是使用这些队列进行任务调度的 ,系统会根据调度任务的需要和系统当前的负载情况动态的创建和销毁线程,不需要我们手动管理线程的销毁。队列负责任务的执行方式;线程是任务的执行通道。

    • 串行队列(Serial Dispatch Queue)
      每次只有一个任务被执行。让任务一个接着一个执行。
    • 并发队列 (Concurrent Dispatch Queue)
      可以让多个任务并发执行,在异步调用的前提下,可以开启线程,实现并发。

    插个题外话

    并发和并行

    • 并发只是假象的同时,是单个处理器的计算机的cpu快速的在不同的程序上切换。
    • 并行是真正意义上的同时,是多个处理器的计算机,各个cpu在同一时间执行不同的程序。


      并发
      并行

      题外话插完了


    队列的创建

    GCD中有三种队列:串行队列,并发队列,主队列(一种特殊的串行队列)。

    //串行队列创建
    
     dispatch_queue_t serialQueue = dispatch_queue_create("com.GCDDemo.testSerial", DISPATCH_QUEUE_SERIAL);
    
    //并发队列创建
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.GCDDemo.testConcurrent", DISPATCH_QUEUE_CONCURRENT);
    创建队列的函数有两个参数,第一个参数表示队列的标识符,类似名字的作用,可为空。第二个参数表示队列的种类,是串行队列还是并发队列,必填。
    除了创建并发队列外,还可以直接获得系统为我们提供的全局并发队列。
    
    //获取全局并发队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    第一个参数表示队列的优先级,一般填默认的就可以。第二个参数是系统预留出来的,填0即可。
    
    //获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    

    任务的创建与执行

    //同步执行任务创建
    dispatch_sync(queue, ^{
        // 任务代码
    });
    
    //异步函数执行任务创建
     dispatch_async(queue, ^{
            //任务代码
        });
    上边函数的block块里是一个任务。
    

    因为有两种调用创建任务的函数和两种队列,再加上一种特殊的主队列。所以我们就一共有六种情况,不同的组合跑出来的效果不一样,先看下这六种组合的区别。

    并发队列 串行队列 主队列
    同步 (sync) 没有开启新线程,串行执行任务 没有开新线程,串行执行任务 如果在主线程中调用:会出现死锁,不执行。在其他线程中调用:没有开新线程,串行执行任务
    异步(async) 会开启新线程,并发执行任务 会开启一条新线程,串行执行任务 没有开启新线程,串行执行任务

    实践运用

    以下测试中所用到的并发队列都用系统提供的全局并发队列

    • 同步调用+并发队列
      同步并发代码.png
    同步并发主.png

    从运行结果中可以看出,同步函数调用,没有开线程,任务都是在主线程进行,而且是顺序串行执行。【在主线程中运行该方法】

    同步并发子.png

    在子线程中运行的话,也一样是没有开新的线程,所有任务都是按照顺序串行执行。

    • 同步调用+串行队列
    同步串行.png 同步串行主线程.png

    主线程中: 同步串行,没有开启新线程,所有任务都要按照属性串行执行,【当前同步函数的调用,必须等到调用的函数执行结束后(即需要将block块里的任务执行完),才能进行下一步】

    同步串行子线程.png

    子线程中,依然是没有开启新的线程。所有任务都是按照顺序串行执行。

    • 异步调用+并发队列
      异步并发代码.png
    异步并发主.png

    从运行结果来看,结束是先于函数调用block里的任务打印出来,说明异步调用,不需要函数(dispatch_async)返回,继续往下执行,从运行结果的线程number来看,开了三条子线程,而且不同的任务分别在不同的线程中执行。说明异步并发开启子线程。

    异步并发子.png

    在子线程中调用异步并发,依然会开启新的子线程。

    • 异步+串行队列
    异步串行代码.png 异步串行主.png

    异步串行在主线程中运行,开启了一条新的线程执行调用函数中加的任务。

    异步串行子.png

    在子线程中执行方法,也会开一条新的子线程执行函数调用中block中任务。

    • 同步调用+主队列
    同步主队列 主线程.png

    程序崩溃 主线程中的任务是在主队列中的,主队列是串行队列 ,任务需要一个个按照顺序执行。在主线程中调用了同步函数,这就相当于队列中加了一个任务1,同步函数的block里又往主队列里加了任务2。主队列中的任务需要依次执行。所以任务2的执行,需要任务1执行完成(同步函数的调用返回)。但是任务1的调用返回必须得block块里的代码(即任务2)执行完毕才可以。所以两者相互等待,造成死锁。【这里有点绕,不知道自己说的是否清楚😂】

    同步主队列 子.png

    如果再子线程里调用该方法,程序就能按照串行执行。不会崩溃。

    由上可以得出死锁发生的条件
    1,队列是串行队列。2,调用同步函数将任务加到自己的队列中。

    • 异步调用+主队列
      异步主队列代码.png
    异步主队列主.png

    虽然是异步,但是也没有开启子线程,调用函数的任务都在主线程中执行。

    异步主队列 子.png

    在子线程中,也没有开启新的线程。

    • 线程间通信,从子线程回到主线程。 通信.png

      一般会在子线程做耗时工作,任务结束后,回到主线程更新UI。

    其它方法

    队列组
    队列组通知代码.png 队列组通知结果.png 队列组enter:leave代码.png 队列组enter:leave结果.png 队列组wait代码.png 队列组wait结果.png
    栅栏函数
    栅栏代码.png 栅栏结果.png

    栅栏函数,顾名思义,通过栅栏将上下的代码隔开,真正的运行效果也是隔开了。【我开发中没有用到过这个函数。不清楚用处】

    延迟执行
    延迟执行.png
    一次执行(单例)

    说到单例,这里有篇谈weak关键字的文章,延伸阅读:弱引用单例

    一次代码.png
    信号量

    信号量
    GCD中信号量有关的有三个函数。

    //创建信号量 参数:信号量的初始值。
    dispatch_semaphore_t  semaphore = dispatch_semaphore_create(0);
    //将信号量减一  参数:信号量,等待时间
    // 等待降低信号量,如果代码执行到这里,信号量的值=0,则阻塞当前线程,知道信号量值大于0时,才会进行执行这里,同时将信号量减一。
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    //信号量加一。
    dispatch_semaphore_signal(semaphore);
    
    • 1,线程同步

      信号量线程同步.png 从图中运行结果来看,异步并发操作,调用异步函数时,应该不用等待该函数返回,直接打印结束。但是在这里加了信号量的控制。当走到dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);时,信号量为0,此时线程阻塞,直到信号量大于0;dispatch_semaphore_signal(semaphore);当走到该行代码时,信号量加1,信号量等待处开始执行。
    • 2,控制最大并发数(这个需求用NSOperationQueue会更方便实现。)

    总结: 一直想把一些东西总结下来,有的时候看到了,觉得懂了,但是到用的时候却不是。经过这次的总结,也发现了一些问题,之后会陆续更新。作为记录,也希望能给需要的人以参考,如果有问题,欢迎指出。谢谢😁😁。
    最后,再次强调下:Demo传送门 🤣😂🤣😂欢迎star

    参考:http://blog.csdn.net/sinat_35512245/article/details/53836580
    https://www.jianshu.com/p/2d57c72016c6
    http://blog.csdn.net/fern_girl/article/details/61197995
    http://www.cnblogs.com/kenshincui/p/3983982.html#3913461

    相关文章

      网友评论

          本文标题:GCD相关

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