GCD详解

作者: kennths | 来源:发表于2020-02-27 17:08 被阅读0次

    GCD是苹果封装的一套C的API,帮助我们能快速使用线程,它可以自动管理线程的创建,调度和销毁等功能,无需开发者自己实现。

    首先它是分为异步和同步线程

    异步线程(dispatch_async)

    会开辟新的线程来执行任务,不用等待当前任务执行完毕,就可以执行下一步任务,不堵塞。

    同步线程(dispatch_sync)

    不会开辟新的线程来执行任务,在当前线程执行,必须等待当前任务执行完毕,才可以执行下一步任务,会堵塞。

    在使用它之前我们来介绍一下队列,队列有分两种,分别是串行队列和并行队列,线程需要搭配队列使用。

    串形队列

    就像一条水管,顾名思义是执行先进先出的原则(FIFO),但是先进真的能先出吗,这个倒是不一定,先进只能说明优先度比较高,可以先进行线程的调度。但是因为受到很多因素的影响,比如当前任务的耗时性,当前CPU调度的等级影响,不一定遵循先进先出。如果耗时性足够小,那么是可以大概判断先进先出的。

    并行队列

    就像开辟了很多条水管,每条水管都可以进,可以同时进行操作,有多少个任务就开辟多少条线程来执行任务。

    那么队列和函数是需要搭配使用的,那么我们就可以分为四种情况,分别是:

    同步函数串行队列

    不会开启新线程,在当前任务执行,任务串行执行,任务会一个接一个执行,会堵塞。

       //开辟串行队列

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

        NSLog(@"start");

        dispatch_sync(queue, ^{

            NSLog(@"1");

        });

        NSLog(@"end");

    //执行顺序为start--1--end

    同步函数并行队列

    不会开启新线程,在当前任务执行,在线程中并发执行,但必须要等到任务执行完,才会往下走,会堵塞。

       //开辟并行队列

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

        NSLog(@"start");

        dispatch_sync(queue, ^{

            for(inti =0; i <10; i ++) {

                NSLog(@"%d",i);

            }

        });

        NSLog(@"end");

        //执行顺序为start--1~9--end

    异步函数串行队列

    开启新线程,任务串行执行,任务会一个接一个执行,但不会堵塞其它线程。

        //开辟串行队列

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

        NSLog(@"start");

        dispatch_async(queue, ^{

            NSLog(@"1");

        });

        NSLog(@"end");

        //执行顺序为start--end--1

    异步函数并行队列

    开启新线程,,任务异步执行,没有顺序,执行顺序与CPU调度有关。

      //开辟并行队列

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

        NSLog(@"start");

        dispatch_async(queue, ^{

            for(inti =0; i <100; i ++) {

                NSLog(@"%d",i);

            }

        });

        NSLog(@"end");

    //执行顺序为start--end--1~99

    OC中还有两个系统自定义的队列,分别是主队列和全局并发队列

    主队列 dispatch_get_main_queue()

    专门用来在主线程上调度任务的队列

    不会开启线程

    如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

    全局并发队列 dispatch_get_global_queue(0, 0)

    是为了给程序员开发方便使用的并发队列

    在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

    但是如果在开辟线程时,因为如果使用全局并发队列,不利于调试,因为里面多加了很多任务。

    那么在使用多线程时又会涉及到另外一个点就是死锁的问题

    // 串行队列

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

        NSLog(@"1");

        // 异步函数

        dispatch_async(queue, ^{

            NSLog(@"2");

            // 同步

            dispatch_sync(queue, ^{

                NSLog(@"3");

            });

            NSLog(@"4");

        });

        NSLog(@"5");

    如上代码,那么代码的执行顺序是怎样的呢,首先会执行1,然后由于有一个异步串行,然后会先执行5,不阻塞,然后开始输出2,那么到了中间执行一个同步串行时候就会发生死锁。因为异步串行 需要一个一个执行,那么就会形成 2->dispatch_sync->4->3这样的执行过程, 而输出4需要先把dispatch_sync给执行完毕了才会输出4,而3由于是在同步串行中的Block代码块里,那么必须要完成3才能完成这个dispatch_sync函数,而3又在等待4执行完毕才可以执行,就形成了相互等待,发生了死锁。这就是主队列与主线程相互等待发生的问题,所以死锁的问题在开发中要注意!

    GCD应用

    单利    dispatch_one

    在开发中经常用来做单利的函数,让对象只初始化一次时经常使用

    延迟 dispatch_after

    在开发中用来做延迟处理,在延迟时间后来执行代码块内的方法

    信号量 dispatch_semaphore_t

    信号量在开发中用的比较多,主要用来处理控制并发数执行,还有可以用作锁的作用,但信号量设置为1的时候,就可以通过+1 -1来控制 当成锁。当设置为1以上时,就可以来用作控制并发数,控制线程可以执行多少个任务。

    栅栏函数 dispatch_barrier_async

    分为dispatch_barrier_sync 和dispatch_barrier_async ,不同的是一个是影响栅栏函数后的是同步还是异步执行。

        dispatch_queue_tconcurrentQueue =dispatch_queue_create("barrierQueue",DISPATCH_QUEUE_CONCURRENT);

        /* 1.异步函数 */

        dispatch_async(concurrentQueue, ^{

            for(NSUIntegeri =0; i <5; i++) {

                NSLog(@"download1-%zd-%@",i,[NSThreadcurrentThread]);

            }

        });

        dispatch_async(concurrentQueue, ^{

            for(NSUIntegeri =0; i <5; i++) {

                NSLog(@"download2-%zd-%@",i,[NSThreadcurrentThread]);

            }

        });

        /* 2. 栅栏函数 */

        dispatch_barrier_sync(concurrentQueue, ^{

            NSLog(@"---------------------%@------------------------",[NSThread currentThread]);

        });

        NSLog(@"加载成功");

        /* 3. 异步函数 */

        dispatch_async(concurrentQueue, ^{

            for(NSUIntegeri =0; i <5; i++) {

                NSLog(@"日常处理3-%zd-%@",i,[NSThreadcurrentThread]);

            }

        });

        NSLog(@"处理中!!");

        dispatch_async(concurrentQueue, ^{

            for(NSUIntegeri =0; i <5; i++) {

                NSLog(@"日常处理4-%zd-%@",i,[NSThreadcurrentThread]);

            }

        });

    由代码可见,栅栏函数将栅栏函数以上的代码和以下代码分割开了,必须先将栅栏函数以上的代码都执行完成后,才会调用栅栏函数下的代码。但是使用栅栏函数的使用有几个注意的点:

    1.只能使用自定义的队列,不能使用全局并发的队列,不然会没效果。

    2.栅栏函数上的代码使用的队列也必须和栅栏函数下代码使用的队列保持一致,否则则只能拦截使用一致的任务,所以在多个自定义队列中,最好不要使用栅栏函数。

    调度组 dispatch_group

    用调度组也可以来控制执行顺序。它有两种方法来实现:

    1创建组方式实现:

    //创建调度组

       dispatch_group_t group = dispatch_group_create();

        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

        dispatch_queue_t queue1 = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);

        // 创建进组任务

        dispatch_group_async(group, queue, ^{

                //进组任务等待

                dispatch_group_wait(group,dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0*NSEC_PER_SEC)));

            NSLog(@"1");

        });

        dispatch_group_async(group, queue1, ^{

           NSLog(@"2");

        });

    //进组任务执行完毕通知

        dispatch_group_notify(group, dispatch_get_main_queue(), ^{

            NSLog(@"3");

        });

    //执行顺序 2->等待3秒->1->3

    2.调度组内部方法

     // 问题: 如果 dispatch_group_enter 多 dispatch_group_leave 不会调用通知

        // dispatch_group_enter 少 dispatch_group_leave  奔溃

        // 成对存在

        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

        dispatch_group_t group = dispatch_group_create();

        dispatch_group_enter(group);

        dispatch_async(queue, ^{

            NSLog(@"1");

            dispatch_group_leave(group);

        });

         dispatch_group_enter(group);

         dispatch_async(queue, ^{

            NSLog(@"2");

            dispatch_group_leave(group);//必须保证一进一出,若多进少出则代码不会发出执行完毕通知,若少进多出,则会报错。

        });

      //当group组数为0时调用

        dispatch_group_notify(group, dispatch_get_main_queue(), ^{

            NSLog(@"3");

        });

    其实dispatch_group_create的本质是信号量,通过创建一个LONG_MAX的信号量来完成创建dispatch_semaphore_create(LONG_MAX);

    Dispatch_soure

    其 CPU 负荷非常小,尽量不占用资源

    联结的优势

    在任一线程上调用它的的一个函数 dispatch_source_merge_data 后,会执行 Dispatch Source 事先定义好的句柄(可以把句柄简单理解为一个 block )

    这个过程叫 Custom event ,用户事件。是 dispatch source 支持处理的一种事件

    句柄是一种指向指针的指针  它指向的就是一个类或者结构,它和系统有很密切的关系

    HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备表述句柄),HICON(图标句柄)等。这当中还有一个通用的句柄,就是HANDLE。

    它有以下几种函数:

    dispatch_source_create 创建源

    dispatch_source_set_event_handler 设置源事件回调

    dispatch_source_merge_data 源事件设置数据

    dispatch_source_get_data 获取源事件数据

    dispatch_resume 继续

    dispatch_suspend 挂起

    dispatch_source_create 有四个参数

    /**

         第一个参数:dispatch_source_type_t type为设置GCD源方法的类型:

    1   DISPATCH_SOURCE_TYPE_DATA_ADD 变量增加

    2   DISPATCH_SOURCE_TYPE_DATA_OR 变量 OR

    3   DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送

    4   DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收

    5   DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存压力 (注:iOS8后可用)

    6   DISPATCH_SOURCE_TYPE_PROC 检测到与进程相关的事件

    7   DISPATCH_SOURCE_TYPE_READ 可读取文件映像

    8   DISPATCH_SOURCE_TYPE_SIGNAL 接收信号

    9   DISPATCH_SOURCE_TYPE_TIMER 定时器

    10 DISPATCH_SOURCE_TYPE_VNODE 文件系统有变更

    11 DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像

         第二个参数:uintptr_t handle Apple的API介绍说,暂时没有使用,传0即可。

         第三个参数:unsigned long mask Apple的API介绍说,使用DISPATCH_TIMER_STRICT,会引起电量消耗加剧,毕竟要求精确时间,所以一般传0即可,视业务情况而定。

         第四个参数:dispatch_queue_t _Nullable queue 队列,将定时器事件处理的Block提交到哪个队列之上。可以传Null,默认为全局队列。注意:当提交到全局队列的时候,时间处理的回调内,需要异步获取UI线程,更新UI....

         */

    我们常用的是通过dispatch_source来弄定时器:

    // 任务执行所指定的队列

            dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

            // 当前定时器源

            self.source=dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0,0, queue);

            // 任务执行开始时间

            dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC);

            // 任务执行间隔时间

            uint64_tinterval =3.0*NSEC_PER_SEC;

            // 给定时器源绑定开始时间、间隔时间以及容忍误差时间

            dispatch_source_set_timer(self.source, start, interval,0);

            // 给定时器源绑定任务

            dispatch_source_set_event_handler(self.source, ^{

                // 内部最好使用weak/strong修饰的self, 防止循环引用

                NSLog(@"----self.timer---");

            });

            // 启动定时器源

            dispatch_resume(self.source);

    相关文章

      网友评论

        本文标题:GCD详解

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