GCD API 理解 (一)

资料先行

GCD 深入理解:第一部分

GCD 深入理解:第二部分

以上两篇文章是关于GCD讲的比较好的文章,翻译自raywenderlich,该网站有很多关于iOS 开发的优秀文章。

引子

转自iOS 开发中有三大进阶性的技术点,分别是GCD、runtime 和runloop。其中GCD用的最多,runtime也有不少使用场景,runloop在系统的API里体现的比较多,项目里实际使用比较少。

一直都想就这三个技术点做一些总结,没事的时候可以回来复习巩固一下,可是记录了很多要写的点,但是文章却是一拖再拖。本文就记录GCD的一些API自己的理解和用法等,遇到新的API也会补充进来。

扩展

pthread 也是C 语言API(pthread现在已经基本看不到有使用的了),而NSThread 是Objective-C对pthread的封装;虽然GCD也是C语言API,但是非常容易使用,而NSOpretion是Objective-C对GCD的进一步封装。这是iOS 中几种多线程技术的关系。

介绍

GCD(Grand Central Dispatch)是OS X与iOS 开发中一种多核开发的解决方案。GCD是在libdispatch框架内,而该框架是iOS 应用默认的已经包含进去了。

苹果是在 OS X 10.6 和 iOS 4 中引入了 GCD,它是低层级的C语言 API。使用GCD,它能够让开发者更加方便、更加容易得使用多核CPU。而且我们开发者不需要再直接跟线程打交道了,只需要向队列中添加代码块即可,而GCD 在后端其实是管理着一个线程池。GCD 不仅决定着我们的代码块将在哪个线程被执行,它还根据可用的系统资源对这些线程进行管理。通过集中的管理线程,来缓解大量线程被创建的问题。

妈妈再也不用担心我们自己来处理线程的创建和销毁了

基本知识

1.串行队列和并发队列

串行队列就是每次只有一个任务执行,当一个任务执行完毕后,才会执行下一个任务。

而并发队列就是同时有多个任务在执行,同时执行的任务哪个先执行完,我们根本不知道。

iOS 中的串行队列有两种:主队列(dispatch_get_main_queue())、通过dispatch_queue_create创建的串行队列

1.1创建串行队列

主队列是系统为我们的应用创建的,我们是没办法创建一个新的主队列的。

以下是一个创建串行队列的例子。

dispatch_queue_tserial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);

其中第一个参数队列的label标记,第二个可以控制创建出来的队列是串行队列还是并行对列。

DISPATCH_QUEUE_SERIAL其实就是一个宏,实际上是NULL。

这也就是为什么,我们经常会看到别人是这样创建串行队列的:

dispatch_queue_tserial_queue = dispatch_queue_create("com.haley.com",NULL);

1.2创建并发队列

第一种创建并发队列的方式就是将上面方法的第二个参数修改为并发参数:

dispatch_queue_tconcurrent_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_CONCURRENT);

第二种方式创建全局并发队列:

dispatch_queue_t concurrent_queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

该方法第一个参数是创建的queue的级别。有四个宏,分别对应不同的级别:

#defineDISPATCH_QUEUE_PRIORITY_HIGH2#defineDISPATCH_QUEUE_PRIORITY_DEFAULT0#defineDISPATCH_QUEUE_PRIORITY_LOW (-2)#defineDISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

第二个参数,是苹果留作以后备用的,一般默认就填0就OK了。

因为DISPATCH_QUEUE_PRIORITY_DEFAULT其实就是等于0,所以我们常常会看到:

dispatch_queue_tconcurrent_queue =  dispatch_get_global_queue(0,0);

2 同步和异步

同步函数就是等待任务的执行,等任务执行完成后再返回。所以同步就意味着在当前线程中执行任务,并不会开启新的线程,不管是串行队列还是并发队列。

dispatch_sync(concurrent_queue, ^{NSLog(@"打印%d----线程:%@", i,[NSThreadcurrentThread]);//在当前线程中执行});

而异步函数就不会等待任务的执行完成,它会立即返回。所以异步也就意味着会开启一个新的线程,所以并不会阻塞当前的线程。

dispatch_async(concurrent_queue, ^{NSLog(@"打印%d----线程:%@", i,[NSThreadcurrentThread]);//在新的线程中执行});

3 总结

GCD的多线程使用都是同步/异步 与串行队列/并发队列组合使用的。

同步异步

串行队列不创建新线程,顺序执行创建新线程,顺序执行

并发队列不创建新线程,顺序执行创建新线程,随机执行(不知哪个任务先执行完)

串行队列无论是同步的执行任务,还是异步的执行任务,任务都是顺序执行的。

串行队列同步任务:

dispatch_queue_tserial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);for(inti =0; i <5; i++) {        dispatch_sync(serial_queue, ^{            NSLog(@"串行%d----线程:%@", i,[NSThread currentThread]);        });    }    dispatch_sync(serial_queue, ^{        NSLog(@"串行最后----线程:%@",[NSThread currentThread]);    });// 打印结果:2016-07-0816:15:13.421PractiseProject[9528:187413] 串行0----线程:{number =1, name = main}2016-07-0816:15:13.421PractiseProject[9528:187413] 串行1----线程:{number =1, name = main}2016-07-0816:15:13.422PractiseProject[9528:187413] 串行2----线程:{number =1, name = main}2016-07-0816:15:13.422PractiseProject[9528:187413] 串行3----线程:{number =1, name = main}2016-07-0816:15:13.422PractiseProject[9528:187413] 串行4----线程:{number =1, name = main}2016-07-0816:15:13.422PractiseProject[9528:187413] 串行最后----线程:{number =1, name = main}

串行队列异步任务:

dispatch_queue_tserial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);for(inti =0; i <5; i++) {        dispatch_async(serial_queue, ^{            NSLog(@"串行%d----线程:%@", i,[NSThread currentThread]);        });    }    dispatch_async(serial_queue, ^{        NSLog(@"串行最后----线程:%@",[NSThread currentThread]);    });// 打印结果:2016-07-0816:16:34.852PractiseProject[9545:188432] 串行0----线程:{number =2, name = (null)}2016-07-0816:16:34.852PractiseProject[9545:188432] 串行1----线程:{number =2, name = (null)}2016-07-0816:16:34.853PractiseProject[9545:188432] 串行2----线程:{number =2, name = (null)}2016-07-0816:16:34.853PractiseProject[9545:188432] 串行3----线程:{number =2, name = (null)}2016-07-0816:16:34.853PractiseProject[9545:188432] 串行4----线程:{number =2, name = (null)}2016-07-0816:16:34.853PractiseProject[9545:188432] 串行最后----线程:{number =2, name = (null)}

串行队列混合任务:

即使串行队列中有同步任务和异步任务,也还是按照添加任务的顺序执行,只不过同步任务在当前线程执行,异步任务在新线程中执行:

dispatch_queue_tserial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);for(inti =0; i <5; i++) {        dispatch_sync(serial_queue, ^{            NSLog(@"串行%d----线程:%@", i,[NSThread currentThread]);        });    }    dispatch_async(serial_queue, ^{        NSLog(@"串行最后----线程:%@",[NSThread currentThread]);    });// 打印结果:2016-07-0816:21:34.545PractiseProject[9588:190084] 串行0----线程:{number =1, name = main}2016-07-0816:21:34.545PractiseProject[9588:190084] 串行1----线程:{number =1, name = main}2016-07-0816:21:34.545PractiseProject[9588:190084] 串行2----线程:{number =1, name = main}2016-07-0816:21:34.545PractiseProject[9588:190084] 串行3----线程:{number =1, name = main}2016-07-0816:21:34.546PractiseProject[9588:190084] 串行4----线程:{number =1, name = main}2016-07-0816:21:34.546PractiseProject[9588:190120] 串行最后----线程:{number =2, name = (null)}

并发队列同步任务:

因为不会开启新的线程,所以就在当前线程中执行,只有一条线程,因此只能顺序执行了。

dispatch_queue_tconcurrent_queue =  dispatch_get_global_queue(0,0);for(inti =0; i <5; i++) {        dispatch_sync(concurrent_queue, ^{            NSLog(@"并行%d----线程:%@", i,[NSThread currentThread]);        });    }    dispatch_sync(concurrent_queue, ^{        NSLog(@"并行最后----线程:%@",[NSThread currentThread]);    });//打印结果:2016-07-0816:26:44.264PractiseProject[9671:193541] 并发0----线程:{number =1, name = main}2016-07-0816:26:44.264PractiseProject[9671:193541] 并发1----线程:{number =1, name = main}2016-07-0816:26:44.265PractiseProject[9671:193541] 并发2----线程:{number =1, name = main}2016-07-0816:26:44.265PractiseProject[9671:193541] 并发3----线程:{number =1, name = main}2016-07-0816:26:44.265PractiseProject[9671:193541] 并发4----线程:{number =1, name = main}2016-07-0816:26:44.265PractiseProject[9671:193541] 并发最后----线程:{number =1, name = main}

并发队列异步任务:

因为异步任务会开启新的线程,所以哪个任务先执行完毕,是不知道的。相同的示例代码多次运行,任务执行完成的顺序是不一样的。这里也可以看出一个GCD的优点,它会复用之前使用过的闲置的线程。

dispatch_queue_tconcurrent_queue =  dispatch_get_global_queue(0,0);for(inti =0; i <5; i++) {        dispatch_async(concurrent_queue, ^{            NSLog(@"并行%d----线程:%@", i,[NSThread currentThread]);        });    }    dispatch_async(concurrent_queue, ^{        NSLog(@"并行最后----线程:%@",[NSThread currentThread]);    });// 打印结果:2016-07-0816:29:03.522PractiseProject[9696:194688] 并发1----线程:{number =4, name = (null)}2016-07-0816:29:03.522PractiseProject[9696:194694] 并发0----线程:{number =3, name = (null)}2016-07-0816:29:03.522PractiseProject[9696:194703] 并发3----线程:{number =6, name = (null)}2016-07-0816:29:03.522PractiseProject[9696:194693] 并发2----线程:{number =5, name = (null)}2016-07-0816:29:03.523PractiseProject[9696:194688] 并发4----线程:{number =4, name = (null)}2016-07-0816:29:03.523PractiseProject[9696:194694] 并发最后----线程:{number =3, name = (null)}

并发队列混合任务:

可以确定的是如果异步任务添加在同步任务后面,则一定会等同步任务执行完毕后,才会执行异步任务;如果异步任务在同步任务前添加,则异步任务什么时候执行完毕也是未知的,但是多个同步任务一定是顺序执行的。

dispatch_queue_tconcurrent_queue =  dispatch_get_global_queue(0,0);for(inti =0; i <5; i++) {        dispatch_sync(concurrent_queue, ^{            NSLog(@"并行%d----线程:%@", i,[NSThread currentThread]);        });    }    dispatch_async(concurrent_queue, ^{        NSLog(@"并行最后----线程:%@",[NSThread currentThread]);    });for(inti =0; i <5; i++) {        dispatch_sync(concurrent_queue, ^{            NSLog(@"并行%d----线程:%@", i,[NSThread currentThread]);        });    }// 打印结果(多次运行打印结果并不相同)2016-07-0816:38:56.273PractiseProject[9805:199354] 并发0----线程:{number =1, name = main}2016-07-0816:38:56.273PractiseProject[9805:199354] 并发1----线程:{number =1, name = main}2016-07-0816:38:56.273PractiseProject[9805:199354] 并发2----线程:{number =1, name = main}2016-07-0816:38:56.273PractiseProject[9805:199354] 并发3----线程:{number =1, name = main}2016-07-0816:38:56.273PractiseProject[9805:199354] 并发4----线程:{number =1, name = main}2016-07-0816:38:56.274PractiseProject[9805:199354] 并发0----线程:{number =1, name = main}2016-07-0816:38:56.274PractiseProject[9805:199354] 并发1----线程:{number =1, name = main}2016-07-0816:38:56.274PractiseProject[9805:199354] 并发2----线程:{number =1, name = main}2016-07-0816:38:56.274PractiseProject[9805:199470] 并发最后----线程:{number =3, name = (null)}2016-07-0816:38:56.274PractiseProject[9805:199354] 并发3----线程:{number =1, name = main}2016-07-0816:38:56.274PractiseProject[9805:199354] 并发4----线程:{number =1, name = main}

注意事项

利用同步/异步任务 和串行队列/并发队列时,需要注意一些情况防止发生死锁。

情形一

在主线程中调度主队列完成一个同步任务,会发生死锁。

- (void)viewDidLoad {    [superviewDidLoad];// Do any additional setup after loading the view, typically from a nib.self.view.backgroundColor= [UIColororangeColor];dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@"串行----线程:%@",[NSThreadcurrentThread]);    });}

如上代码,界面永远不会加载出来,里面的NSLog永远也不会执行。原因是ViewDidLoad是在主队列的主线程中执行,执行到dispatch_sync 时会阻塞住,等待dispatch_sync中的打印任务执行完毕。而dispatch_sync又会等viewDidLoad执行完毕,再开始执行,因此就互相等待发生死锁。

情形二

在串行队列的同步任务中再执行同步任务,会发生死锁。

dispatch_queue_tserial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);dispatch_sync(serial_queue, ^{NSLog(@"串行1----线程:%@",[NSThreadcurrentThread]);dispatch_sync(serial_queue, ^{NSLog(@"串行2----线程:%@",[NSThreadcurrentThread]);        });    });

上面示例中的NSLog(@"串行1----线程:%@",[NSThread currentThread]);会打印,

但是NSLog(@"串行2----线程:%@",[NSThread currentThread]);永远也不会执行。

因为串行队列一次只能执行一个任务,执行完毕返回后,才会执行下一个任务,而外层任务的完成需要等待内层任务的结束,而内层任务的开始需要等外层任务结束。

其实情形一是情形二的一种特殊情况。

情形三

在串行队列的异步任务中再嵌套执行同步任务,也会发生死锁。

dispatch_queue_tserial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);dispatch_async(serial_queue, ^{NSLog(@"串行异步----线程:%@",[NSThreadcurrentThread]);dispatch_sync(serial_queue, ^{NSLog(@"串行2----线程:%@",[NSThreadcurrentThread]);        });        [NSThreadsleepForTimeInterval:2.0];    });

同样的,由于串行队列一次只能执行一个任务,任务结束后,才能执行下一个任务。

所以异步任务的结束需要等里面同步任务结束,而里面同步任务的开始需要等外面异步任务结束,所以就相互等待,发生死锁了。

第一篇就到这里了,下一篇记录GCD的其他API。Have Fun!

文/Haley_Wong(简书作者)

原文链接:http://www.jianshu.com/p/2b51728e1282

著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

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

推荐阅读更多精彩内容