GCD详解

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);

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容