GCD与多线程编程

【Xcode-Men】Hi,我们团队的井小二童鞋给我们取了个队名:Xcode-Men,简称X-Men,是不是屌屌的。我们开这个博客,是想记录下记录下每位小伙伴的成长,以及我们团队的成长。我们的团队很年轻,准90后和90后已经成了我们的主力。希望他们在不久的将来,能脱颖而出;也希望他们离开团队时,能非常自豪地说自己曾经是X-Men的一员,这是一种情怀,情怀【噫,好像不能用表情】。

这是我们团队的第一篇技术总结,由王瑞华童鞋撰写。总结了自己在开发过程GCD的一些心得。欢迎大家吐槽。

最近在开发股神的项目中用到很多GCD,所以总结了一下GCD的一些使用。

1. 什么是GCD


官方文档是这么说的:

>Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in iOS and OS X.

大致意思是说,GCD是异步执行任务的技术,提供语言特征、运行时库、系统全面的改进的多核硬件上的支持。开发者只需要定义想执行的任务并加入到队列中就可以了。

也就是说,GCD用我们难以置信的非常简洁的记述方法,实现了极为复杂繁琐的多线程编程,可以说是一项划时代的技术。

同时它具有很多优点:

- 直观而简单的编程接口

- 提供自动和整体的线程池管理

- 提供汇编级调优的速度

- 更加高效的使用内存

- 不会trap内核

- 异步分派任务到dispatch queue,不对导致queue死锁

- 伸缩性强

- serial dispatch queue比锁和其他同步原语更加高效

2 GCD APIs


2.1串行队列Serial Diapatch Queue

创建串行队列,对于第二个参数可为DISPATCH_QUEUE_SERIAL或者NULL,队列中操作会按顺序执行。

dispatch_queue_t myQueue = dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_SERIAL);

dispatch_async(myQueue, ^{

    NSLog(@"1");

});

dispatch_async(myQueue, ^{

    NSLog(@"2");

});

dispatch_async(myQueue, ^{

    NSLog(@"3");

});

以上语句的输出结果为:

2016-06-27 15:11:40.612 TestGCD[34781:4486699] 1

2016-06-27 15:11:40.613 TestGCD[34781:4486699] 2

2016-06-27 15:11:40.613 TestGCD[34781:4486699] 3

三者相互依赖,串行执行。

2.2并行队列Concurrent Diapatch Queue

往队列中追加的操作,没有相互依赖关系,执行会放到不同的线程中,执行的先后顺序未知。

dispatch_queue_t myQueue

= dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(myQueue, ^{

    NSLog(@"1");

});

dispatch_async(myQueue, ^{

    NSLog(@"2");

});

dispatch_async(myQueue, ^{

    NSLog(@"3");

});

运行后发现日志为:

2016-06-27 15:18:23.895 TestGCD[34834:4493908] 2

2016-06-27 15:18:23.895 TestGCD[34834:4493899] 1

2016-06-27 15:18:23.895 TestGCD[34834:4493934] 3

当然这只是其中的一种情况,按照排列组合计算,可以产生6种不同的顺序。

如前所诉,concurrent dispatch queue并行执行多个处理,而serial只能执行1个追加处理。虽然一个serial queue只能执行一个,但是可以创建多个,代价便是产生多个线程,过多的线程会消耗大量内存,频繁的上下文切换会大幅降低性能。

2.3 Main Dispatch Queue/Global Dispatch Queue

在我们不打算自己生成dispatch queue的情况下,系统会为我们准备两个,那就是Main Dispatch Queue(就是serial queue),Global Dispatch Queue(就是concurrent queue)。

- Main顾名思义就是主线程,所有的操作都会追加到主线程去执行,大多都是用户界面的更新。

- Global是说有程序使用的concurrent queue,同时它具有四个优先级分别为High,Default,Low,Background。通过XNU内核根据优先级来调度线程执行。

说完了队列再来说说,操作队列的一些方法


3.1 dispath_set_target_queue

变更生成的dispatch queue的执行优先级使用dispatch_set_target_queue函数,在实际的工作里我倒是没用到过该方法。

dispatch_queue_t mySerialQueue = dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t myGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

dispatch_set_target_queue(mySerialQueue, myGlobalQueue);

dispatch_async(mySerialQueue, ^{

    NSLog(@"hello");

});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"world");

});

执行结果如下,根据结果看出该方法修改了优先级。

2016-06-27 15:46:05.972 TestGCD[34901:4517280] world

2016-06-27 15:46:05.973 TestGCD[34901:4517287] hello

3.2 dispatch_after

这个在工作中倒是常常用到,我常用在tableview reloaddata场景中,因为有一些诸如点赞动画之类的,加入该方法是的点赞动画执行完毕后的0.5~1s再执行reloadData操作。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    NSLog(@"what happened");

});

// 输出

2016-06-27 15:51:07.921 TestGCD[34918:4521806] hello

2016-06-27 15:51:11.216 TestGCD[34918:4521762] what happened

大致该语句执行是在3秒之后,也就是3秒之后追加block到main queue。这个时间并不是绝对的,block只是追加到了main runloop中,而main queue可能有大量其他处理,会使得时间变长。第一个参数是指定的dispatch_time_t类型。该值有dispatch_time或dispatch_walltime。前者通常用于相对时间,后者为绝对时间。如希望在2016/11/11 -0:0执行。

dispatch_time_t getDispatchTimeByData(NSDate *date) {

    NSTimeInterval interval;

    double second, subsecond;

    struct timespec time;

    dispatch_time_t milestone;

    interval = [date timeIntervalSince1970];

    subsecond = modf(interval, &second);

    time.tv_sec = second;

    time.tv_nsec = subsecond * NSEC_PER_SEC;

    milestone = dispatch_walltime(&time, 0);

    return milestone;

}

NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:5];

dispatch_after(getDispatchTimeByData(date), dispatch_get_main_queue(), ^{

    NSLog(@"happen sth");

});

// 输出

2016-06-27 16:04:06.949 TestGCD[34946:4531114] what happened

2016-06-27 16:04:09.439 TestGCD[34946:4531114] happen sth

3.3 dispatch_group

如果有3个操作(A,B,C)需要在并行执行完A,B之后再执行C操作,可以有多个实现方式,当然可以通过添加依赖关系addDependency来实现。通过dispatch_group的方式依然可以实现。

无论向什么样的dispatch queue中追加处理,使用dispatch group都能监视到所有处理的结束,就可以将结束的处理追加到dispatch queue中,这是使用dispatch group的原因。

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{

    NSLog(@"A");

});

dispatch_group_async(group, queue, ^{

    NSLog(@"B");

});

dispatch_group_notify(group, queue, ^{

    NSLog(@"C");

});

// 输出

2016-06-28 09:07:18.261 TestGCD[35461:4595668] B

2016-06-28 09:07:18.261 TestGCD[35461:4595678] A

2016-06-28 09:07:18.262 TestGCD[35461:4595668] C

3.4 dispatch_barrier_async

在访问数据库或者文件时,有可能发生数据竞争。此方法的作用便是在并发队列中,完成在它之前提交到队列中的任务后打断,单独执行其block。起到了一个线程锁的作用。同样适用于上节中的,AB,C的执行顺序问题。

dispatch_queue_t queue = dispatch_queue_create("com.my.wangruih", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

    NSLog(@"A");

});

dispatch_async(queue, ^{

    NSLog(@"B");

});

dispatch_barrier_async(queue, ^{

    NSLog(@"C");

});

dispatch_async(queue, ^{

    NSLog(@"D");

});

// 输出

2016-06-28 09:21:43.864 TestGCD[35515:4607006] A

2016-06-28 09:21:43.864 TestGCD[35515:4607015] B

2016-06-28 09:21:43.865 TestGCD[35515:4607015] C

2016-06-28 09:21:43.865 TestGCD[35515:4607015] D

3.5 dispatch_sync

它以为着事件是同步发生的,也就是指定的block同步追加到指定的dispatch queue中。在追加block结束之前,dispatch_sync函数会一直等待。这里有一个经常提及的问题来考察对同步的理解。

题目如下,输出结果是什么,为什么会这样。

NSLog(@"1");

dispatch_sync(dispatch_get_main_queue(), ^{

    NSLog(@"2");

});

NSLog(@"3");

输出结果是:

2016-06-28 09:28:10.713 TestGCD[35528:4611901] 1

原因:由于main queue是串行queue,采用FIFO执行任务,也就是block操作加在了队列之后,dispathc_sync堵塞了主线程等待block语句完成后执行main thread,但block语句由于线程阻塞永不会执行,所以导致一直等待死锁。

3.6 dispatch_asyc

与sync不同,它是非同步的追加到指定的dispatch queue中。dispatch_async函数不做任何等待。

这里也有一个对于该函数理解的题目,如下,推断他的执行顺序

NSLog(@"1");

dispatch_async(dispatch_get_main_queue(), ^{

    NSLog(@"2");

});

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

NSLog(@"3");

}

打印log日志为:

2016-06-28 09:39:16.048 TestGCD[35575:4619720] 1

2016-06-28 09:39:16.049 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.049 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.050 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.050 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.050 TestGCD[35575:4619720] 3

2016-06-28 09:39:16.059 TestGCD[35575:4619720] 2

也就是说**NSLog(@"2");**永远会在for循环之后执行。原因:main queue为串行队列,遵循FIFO原则,同时为异步执行,异步block添加到队列中的**“不等待”**立刻执行for循环,在下一次runloop时才会执行block语句块的内容。

3.7 dispatch_semaphore_t

dispatch semaphore是持有计数的信号,该计数是多线程中的计数类型信号。所谓信号,类似于过北京西站地铁口安检时常用的手牌。可以通过时举起手牌,不可通过时放下手牌。而在dispatch semaphore中,使用计数来实现该功能。计数为0时等待,计数为1或者大于1时,减去1而不等待。

主要涉及三个函数 dipatch_semaphore_create()dispatch_semphore_signaldispatch_semaphore_wait.

对于以上三个函数通常都用停车位来解释,dipatch_semaphore_create()说明了初始车位数,没调用一次dispatch_semphore_signal剩余车位数就增加一个,每调用dispatch_semaphore_wait剩余车位数减少一个,等车位数为0时,再来车(即调用dispatch_semaphore_wait)就只能等车位。

该函数同样能解决上节中A,B,C的执行依赖问题。

dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(0);

dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"A");

dispatch_semaphore_signal(semaphore1);

});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    NSLog(@"B");

    dispatch_semaphore_signal(semaphore2);

});

dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);

NSLog(@"C");

// 输出

2016-06-28 10:01:33.658 TestGCD[35622:4632780] B

2016-06-28 10:01:33.658 TestGCD[35622:4632775] A

2016-06-28 10:01:33.660 TestGCD[35622:4632744] C

3.8 dispatch_apply

dispatch_apply函数是dispatch_sync函数和dispatch group的关联API。该函数按指定的次数将指定的block追加到指定的dispatch queue中,并等待全部处理执行完成。

dispatch_apply(5, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {

    NSLog(@"%zu", index);

});

NSLog(@"done");

// 输出

2016-06-28 10:10:25.110 TestGCD[35649:4638944] 3

2016-06-28 10:10:25.110 TestGCD[35649:4638889] 0

2016-06-28 10:10:25.110 TestGCD[35649:4638940] 2

2016-06-28 10:10:25.110 TestGCD[35649:4638934] 1

2016-06-28 10:10:25.111 TestGCD[35649:4638889] 4

2016-06-28 10:10:25.111 TestGCD[35649:4638889] done

第一个参数为重复的次数,第二个参数为追加对象的dispatch queue,第三个参数为追加的处理。由于*dispatch_apply*函数也与*dispatch_sync*函数相同,会等待处理执行结束,推荐在*dispatch_async*函数中非同步地执行*dispatch_apply*函数

参考


《Objective-C高级编程 iOS与OS X多线程》

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

推荐阅读更多精彩内容

  • 最近颇花了一番功夫把多线程GCD人的一些用法总结出来,一来帮自己巩固一下知识、二来希望能帮到对这一块还迷茫...
    人活一世阅读 278评论 1 1
  • 一. 重点: 1.dispatch_queue_create(生成Dispatch Queue) 2.Main D...
    BestJoker阅读 1,574评论 2 2
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,016评论 0 4
  • iOS多线程之GCD 什么是GCD GCD(grand central dispatch) 是 libdispat...
    comst阅读 1,196评论 0 0
  • 所有那些時刻將消失在時間裡,像雨中的淚水。
    荒木覺阅读 108评论 0 3