OC -> GCD

iOS - GCD

概念

big

GCD (Grand Central Dispatch): 伟大的的中枢调节器; GCD 是苹果公司开发的一个创建子线程的一个方法,主要为多核的并行开发运算提出解决方案,利用 GCD 可以是的 CPU 的内核运用的更加充分,同时 GCD 也会自动管理线程中的生命周期。

small

1.队列:队列是一种线性表,队列的上下都是开口的与栈相反,队列中遵循的原则是FIFO即为先进先出,队列是在表尾进行添加操作,表头进行删除操作即先进先出后进后出。

2.同步:同步是指的是在调用方法时,按照顺序的执行一些代码。在第一个方法没有执行完的时候第二个方法是不会进行的。

3.异步:异步和同步相反,调用方法时,当没有收到第一个方法调用的返回值时,第二个方法也可以执行。

4.串行:程序运行时,程序会按照顺序执行代码,只是存在一个运行上下文。

5.并发:程序运行时,程序存在多个运行上下文,可以通过这些上下文执行不同的代码。

多线程编程

我们研究多线程编程之前,需要知道当我们执行一段代码的时候 CPU是如何执行的。

Cup 执行过程



通过上图我们要了解一个概念叫做上下文切换,上下文切换是 OSX 和 iOS 的核心 XNU 内核在发生操作系统事件时会切换执行路径,例如 CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原 CPU 寄存器等信息,继续执行切换路径的 CPU 命令列。叫做上下文切换。

利用这种编程的方式叫做多线程编程,但是通过多线程编程同样的也会产生很多的问题具体问题如下

  1. 多线程更新相同的资源会导致数据的不一致。

    具体表现为两个线程同时对一个数据进行更新,而两个线程得到的数据不相同从而导致数据的不一致性。
  2. 死锁问题,多个线程之间相互持有,造成持续等待。

    如下代码就发生了线程的死锁,程序先执行 1,这时候同步线程会让程序进入等待等 2 执行完了之后再执行 3,但是 sync 有产生了队列,队列会将操作放到对尾 2 等到 3 执行完了在执行,而 3 又在等待 2 执行完了它在执行。所以造成了死锁现象。
  3. 过多的线程会消耗大量的内存。

    因为过多的线程会使得 CPU 大量的调用“上下文切换”从而使得 CPU 的消耗巨大,从而导致程序的卡顿。
    printf("1");
    dispatch_sync(dispatch_get_main_queue(),^{
        NSLog(@"线程发生了死锁");
        printf("2");
    })
    printf("3");

GCD Code

dispatch_queue_create,dispatch_sync 和 dispatch_async

通过 dispatch_queue_create 函数可以生成 Dispatch Queue 根据参数的不同可以生成 Serial 和 Concurrent 两种类型的 Dispatch Queue。

dispatch_queue_t queue = dispatch_queue_create("www.kong.com",NULL);

..create后面的两个参数:<br>第一个参数是队列的名称<br>第二个参数传NULL的时候是创建一个串行的队列。传DISPATCH_QUEUE_CONCURRENT`的时候是创建一个并发队列。

这样我们分四种情况对 GCD 进行讨论,同步串行,同步并发,异步串行,异步并发.

** 同步串行**


    dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", NULL);
    dispatch_sync(queue ,^{
        NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_sync(queue ,^{
        NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_sync(queue ,^{
        NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_release(queue);//ARC 条件下我们需要将其进行释放。

打印的结果为

a当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
b当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
c当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}

创建了串行队列,通过同步的方式添加到Dispatch Queue 等待队列中,没有开辟新的线程,所有的打印都是在主线程中进行,队列 queue 按照 abc 的顺序添加到等待队列中,也就是 FIFO 原则,出队列的时候也是满足该原则实现了先进先出,后进后出,打印的时候按顺序进行打印。

** 同步并发**


    dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue ,^{
        NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_sync(queue ,^{
        NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_sync(queue ,^{
        NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_release(queue);//ARC 条件下我们需要将其进行释放。

打印的结果为

a当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
b当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
c当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}

创建了并发的队列添加到队列中的时候用 sync 同步的方式将其追加到Dispatch Queue 中 没有开辟新的线程,所有的数据处理仍然是在主线程中进行处理,Dispatch Queue 按照追加的顺序(队列 FIFO)的方式执行处理。

** 异步串行**


    dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", NULL);
    dispatch_async(queue ,^{
        NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_async(queue ,^{
        NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_async(queue ,^{
        NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_release(queue);//ARC 条件下我们需要将其进行释放。

打印的结果为

a当前的线程为 <NSThread: 0x12f505470>{number = 2, name = (null)} (这里的 name = null,可以通过[NSThread currentThread]来设置)
b当前的线程为 <NSThread: 0x12f505470>{number = 2, name = (null)}
c当前的线程为 <NSThread: 0x12f505470>{number = 2, name = (null)}

创建串行的Queue,然后通过异步的方式添加到队列中去,使得 Dispatch Queue开辟了一个新的线程,但是还是通过同步队列的方式来进行执行,都是在dispatch_async 创建出来的线程中按照顺序执行队列中的操作。

** 异步并发**


 dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue ,^{
        NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_async(queue ,^{
        NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_async(queue ,^{
        NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_release(queue);//ARC 条件下我们需要将其进行释放。

打印的结果为:

a当前的线程为 <NSThread: 0x147d41a20>{number = 2, name = (null)}
c当前的线程为 <NSThread: 0x147d41a20>{number = 2, name = (null)}
b当前的线程为 <NSThread: 0x147e51ec0>{number = 3, name = (null)}

创建的是并发队列,通过异步的方式将其追加到 Dispatch Queue 中,就会使得 CPU 开辟新的线程来执行代码块 Blcok 中的代码。从而达到程序流畅的目的。

GCD中其他的一些方法

** 利用 global 创建线程**


    //通过dispatch_get_global_queue 创建的是并发的队列
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        执行耗时操作       
     });

*对于 main dispatch_queue 和 global dispatch_queue 执行 dispatch_retain 函数和 dispatch_release 函数不会引起任何的变化,也不会有任何的问题。
当我们使用 dispatch_queue_create 的时候要考虑到在 ARC 中何时执行 dispatch_retain 和 dispatch_release 两个函数方法。

** dispatch_after**


    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"3秒后执行该方法");
    });

通过这种方法延迟3秒执行,要知道这种方法的实现是在3秒后将 block 中的代码追加到 Dispatch Queue 中,并不是在指定的时间后进行处理。不像 NSTimer 里面的设置在什么时间后进行处理。

** dispatch_group**


    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group =  dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行 block 1 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行 block 2 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行 block 3 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行 block 4 %@",[NSThread currentThread]);
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"执行完 group 执行的操作");
    });

通过运用组的话,可以先执行组中的内容,然后当组中的内容执行完了之后用 group 的 notify 方法可以在执行 block 中的方法。这种方法用于顺序执行,例如下载图片的时候,下载多张图片,然后进行拼接到一起,时候可以用这种方法将两个图片都下载下来,然后在 group_notify中对图片进行拼接,然后得到结果。group_notify 也是起到一个追加的作用,将 block 中的内容追加到 Dispatch Queue 的后面。等到 group 中的队列执行完后在执行。

** dispatch_barrier_async**


    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"向数据库中写入数据");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"从数据库中进行数据的读取"); 
    });
    dispatch_async(queue, ^{
        NSLog(@"从数据库中写入数据");
    });

通过 dispatch_barrier_async 方法可以使当前的线程只执行 barrier_async(block)中的操作在两次写入操作的时候如果不用 dispatch_barrier_async 方法的话就会导致读取数据的同时也在写入数据,就导致了获取的数据不准确,此时的 dispatch_barrier_async 就可以起到线程锁的作用,保障读写的安全。

** dispatch_apply**


  NSArray *applyArray = @[@1,@2,@3,@4,@5,@6];
    dispatch_apply([applyArray count], queue, ^(size_t index) {
        NSLog(@"%zu   %@",index,[applyArray objectAtIndex:index]);
    });

这段代码是把一项任务提交到队列中多次执行,队列的串行并行由创建的队列所决定。这里类似一个循环遍历的功能,这样将不想关的循环调到后台线程执行,会将执行效率大大提高。

GCD 底层实现。

GCD 是用于管理追加 Block 的 C 语言层实现 FIFO队列
里面主要运用了 libdispatch,Libc(pthreads),XNU内核。
GCD中的 API 全部为包含在 libdispatch 库中的 C 语言函数,Dispatch Queue通过结构体和链表,被实现为 FIFO队列 ,FIFO队列管理是通过 dispatch_async 等函所追加的 Block。

GCD 实现过程

Block 并不是直接加入 FIFO队列中,而是先加入 Dispatch Continuation 这一个 dispatch_continuation_t 类型的结构体中,然后在加入 FIFO队列,该 Dispatch Continuation 用于记忆 Block 所属的 DispatchGroup 和其他的一些信息。 当 Dispatch Queue 中执行 Block 的时候,libdispatch 从 Global Dispatch Queue 自身的 FIFO 队列中取出 Dispatch Continuation ,调用 pthread_workqueue_additem_up 函数。将该 Global Dispatch Queue 自身、符合其优先级的 workqueue 信息以及为执行 Dispatch Continuation 的回调函数等传递给参数。 workqueue 最终决定是否生成线程。
workqueue 的线程执行 pthread_workqueue 函数,该函数调用 libdispatch 的回调函数,在该回调函数中执行加入到 Dispatch Continuation 的 Block。
Block 执行结束后,进行通知 DispatchGroup 结束,释放 Dispatch Continuation 等处理,开始准备执行加入到 Global Dispatch Queue中的下一个 Block。

说明:

本文参考

图书《iOS与 OSX 多线程和内存管理》

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

推荐阅读更多精彩内容

  • 目录(GCD): 关键词 混淆点 场景应用 总结 1. 关键词 线程概念: 独立执行的代码段,一个线程同时间只能执...
    Ryan___阅读 1,259评论 0 3
  • 3.GCD GCD的全称是Grand Central Dispatch,提供了非常多的纯C语言的函数 GCD的优势...
    Mario_ZJ阅读 472评论 0 0
  • cp 命令 名称: cp —— copy files and directories 摘要 cp [options...
    Manford阅读 268评论 0 0
  • 分享人:蔡永坚 1. 问题描述:委托。 功能要求:已知有一个字符串数组,将该数组里的长度为2字符串选出来; 没学习...
    胡諾阅读 195评论 0 0
  • 这周作业是本月的最后一次作业了。也是我加入不写就出局的第四个月,这个月有个好消息就是我加入的不讲就出局第二期...
    dingding_74be阅读 198评论 0 0