GCD

GCD为Grand Central Dispatch的缩写。[1]

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。

设计

GCD是一个替代诸如NSThread等技术的很高效和强大的技术。GCD完全可以处理诸如数据锁定和资源泄漏等复杂的异步编程问题。GCD的工作原理是让一个程序,根据可用的处理资源,安排他们在任何可用的处理器核心上平行排队执行特定的任务。这个任务可以是一个功能或者一个程序段。

GCD仍然在一个很低的水平使用线程,但是它不需要程序员关注太多的细节。GCD创建的队列是轻量级的,苹果声明一个GCD的工作单元需要由15个指令组成。也就是说创造一个传统的线程很容易的就会需要几百条指令。

GCD中的一个任务可被用于创造一个被放置于队列的工作项目或者事件源。如果一个任务被分配到一个事件源,那么一个由功能或者程序块组成的工作单元会被放置于一个适当的队列中。苹果公司认为GCD相比于普通的一个接一个的执行任务的方式更为有效率。

注:The main queue is automatically created by the system and associated with your application’s main thread.










队列分为两种:

串行队列,并发队列

任务分为两种:

同步任务,异步任务

创建串行Dispatch Queue  —— FIFO 先进先出

应用的任务需要按特定顺序执行时,就需要使用串行Dispatch Queue,串行queue每次只能执行一个任务。你可以使用串行queue来替代锁,保护共享资源 或可变的数据结构。和锁不一样的是,串行queue确保任务按可预测的顺序执行。而且只要你异步地提交任务到串行queue,就永远不会产生死锁

利用dispatch_queue_create函数创建串行queue

并发dispatch queue

它可以同时并行地执行多个任务,不过并发queue仍然按先进先出的顺序来启动任务。并发queue会在之前的任务完成之前就出列下一个任务并开始执行。并发queue同时执行的任务数量会根据应用和系统动态变化,各种因素包括:可用核数量、其它进程正在执行的工作数量、其它串行dispatch queue中优先任务的数量等.

系统给每个应用提供三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。你不需要显式地创建这些queue,使用dispatch_get_global_queue函数来获取这三个queue:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//#define DISPATCH_QUEUE_PRIORITY_HIGH 2  高

//#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0  中

//#define DISPATCH_QUEUE_PRIORITY_LOW (-2)  低

你必须显式地创建和管理所有你使用的串行queue,应用可以创建任意数量的串行queue,但不要为了同时执行更多任务而创建更多的串行queue。如果你需要并发地执行大量任务,应该把任务提交到全局并发queue

//串行队列

dispatch_queue_t queue1 =  dispatch_queue_create("myqueue", NULL);

使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。

使用dispatch_get_main_queue函数获得应用主线程关联的串行dispatch queue

使用dispatch_get_global_queue来获得共享的并发queue

要执行一个任务,你需要将它添加到一个适当的dispatch queue,你可以单个或按组来添加,也可以同步或异步地执行一个任务,也。一旦进入到queue,queue会负责尽快地执行你的任务。一般可以用一个block来封装任务内容。

添加单个任务到queue

1> 异步添加任务

你可以异步或同步地添加一个任务到Queue,尽可能地使用dispatch_async或dispatch_async_f函数异步地调度任务。因为添加任务到Queue中时,无法确定这些代码什么时候能够执行。因此异步地添加block或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。特别是应用主线程一定要异步地 dispatch 任务,这样才能及时地响应用户事件

2> 同步添加任务

少数时候你可能希望同步地调度任务,以避免竞争条件或其它同步错误。 使用dispatch_sync和dispatch_sync_f函数同步地添加任务到Queue,这两个函数会阻塞当前调用线程,直到相应任务完成执行。注意:绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做。

// 调用前,查看下当前线程

NSLog(@"当前调用线程:%@", [NSThread currentThread]);

// 创建一个串行queue

dispatch_queue_t queue2 = dispatch_queue_create("我的串行队列", NULL);

dispatch_async(queue2, ^{

NSLog(@"开启了一个异步任务,当前线程:%@", [NSThread currentThread]);

});

dispatch_sync(queue2, ^{

NSLog(@"开启了一个同步任务,当前线程:%@", [NSThread currentThread]);

});

输出:

2015-09-26 15:03:00.422 test[1450:78773] 当前调用线程:{number = 1, name = main}

2015-09-26 15:03:00.429 test[1450:78856] 开启了一个异步任务,当前线程:{number = 2, name = (null)}

2015-09-26 15:03:00.429 test[1450:78773] 开启了一个同步任务,当前线程:{number = 1, name = main}

注 :同步任务是在主队列上执行

调用dispatch_apply或dispatch_apply_f函数来替换循环。

下面代码使用dispatch_apply替换了for循环,你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。第一次迭代这个参数值为0,最后一次值为count - 1

dispatch_queue_t queue3 = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

size_t count = 10;

dispatch_apply(count, queue3, ^(size_t i) {

[NSThread sleepForTimeInterval:1.0];

NSLog(@"线程: %@  <<<<<<<< %zd ",[NSThread currentThread], i);

});

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

[NSThread sleepForTimeInterval:1.0];

NSLog(@"线程:%@ >>>> %d",[NSThread currentThread], i);

}

2015-09-26 15:55:33.048 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 0

2015-09-26 15:55:34.148 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 1

2015-09-26 15:55:35.244 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 2

2015-09-26 15:55:36.337 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 3

2015-09-26 15:55:37.430 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 4

2015-09-26 15:55:38.528 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 5

2015-09-26 15:55:39.609 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 6

2015-09-26 15:55:40.707 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 7

2015-09-26 15:55:41.774 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 8

2015-09-26 15:55:42.867 test[2036:108847] 线程: {number = 1, name = main}  <<<<<<<< 9

2015-09-26 15:55:43.954 test[2036:108847] 线程:{number = 1, name = main} >>>> 0

2015-09-26 15:55:45.039 test[2036:108847] 线程:{number = 1, name = main} >>>> 1

2015-09-26 15:55:46.129 test[2036:108847] 线程:{number = 1, name = main} >>>> 2

2015-09-26 15:55:47.211 test[2036:108847] 线程:{number = 1, name = main} >>>> 3

2015-09-26 15:55:48.298 test[2036:108847] 线程:{number = 1, name = main} >>>> 4

2015-09-26 15:55:49.387 test[2036:108847] 线程:{number = 1, name = main} >>>> 5

2015-09-26 15:55:50.483 test[2036:108847] 线程:{number = 1, name = main} >>>> 6

2015-09-26 15:55:51.582 test[2036:108847] 线程:{number = 1, name = main} >>>> 7

2015-09-26 15:55:52.583 test[2036:108847] 线程:{number = 1, name = main} >>>> 8

2015-09-26 15:55:53.583 test[2036:108847] 线程:{number = 1, name = main} >>>> 9

可以看出,这些迭代是并发执行的

和普通for循环一样,dispatch_apply和dispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程,主线程中调用这两个函数必须小心,可能会阻止事件处理循环并无法响应用户事件。所以如果循环代码需要一定的时间执行,可以考虑在另一个线程中调用这两个函数。如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。

dispatch_apply 添加的是同步任务,即把串行队列运行在main_queue上,会阻塞主线程

// 异步下载图片

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];

UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];

// 回到主线程显示图片

dispatch_async(dispatch_get_main_queue(), ^{

NSLog(@"--->>>%d",image);

});

});

我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。

dispatch_queue_t q =  dispatch_queue_create("queue1", 0);

dispatch_queue_t q2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(q, ^{

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

[NSThread sleepForTimeInterval:1];

NSLog(@"------%d",i);

}

});

dispatch_suspend(q);//执行到这里被挂起,下面的任务不执行

dispatch_async(q, ^{

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

[NSThread sleepForTimeInterval:1];

NSLog(@"=====%d",i);

}

});

dispatch_resume(q);//这里恢复第二个任务开始执行

// 异步下载图片

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

// 下载第一张图片

NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";

NSURL *url = [NSURL URLWithString:url1];

NSData *data = [NSData dataWithContentsOfURL:url];

UIImage *image1 = [UIImage imageWithData:data];

// 下载第二张图片

NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";

NSURL *ur2 = [NSURL URLWithString:url2];

NSData *data2 = [NSData dataWithContentsOfURL:ur2];

UIImage *image2 = [UIImage imageWithData:data2];

//回到主线程显示图片

dispatch_async(dispatch_get_main_queue(), ^{

NSLog(@"----加载完成------%@---%@",image1,image2);

});

});

虽然这种方案可以解决问题,但其实两张图片的下载过程并不需要按顺序执行,并发执行它们可以提高执行速度。有个注意点就是必须等两张图片都下载完毕后才能回到主线程显示图片。Dispatch Group能够在这种情况下帮我们提升性能。下面先看看Dispatch Group的用处:

我们可以使用dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。

以上这种方案运行在一个线程上

dispatch_group_t group = dispatch_group_create();

//关联一个任务到group

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSLog(@"执行第一个任务...");

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

NSLog(@"当前线程:%@  ————   第一个任务 %d",[NSThread currentThread],i);

}

});

// 关联一个任务到group

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSLog(@"执行第二个任务...");

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

NSLog(@"当前线程:%@  ===== 第二个任务 %d",[NSThread currentThread],i);

}

});

// 等待组中的任务执行完毕,回到主线程执行block回调

dispatch_group_notify(group, dispatch_get_main_queue(), ^{

NSLog(@"任务执行完毕...");

// 千万不要在异步线程中自动释放UIImage,因为当异步线程结束,异步线程的自动释放池也会被销毁,那么UIImage也会被销毁

});

NSLog(@" 我是主线程,都给我闪开 : %@",[NSThread currentThread]);

2015-09-26 18:26:53.606 test[2972:151236] 执行第二个任务...

2015-09-26 18:26:53.606 test[2972:151235] 执行第一个任务...

2015-09-26 18:26:53.606 test[2972:151166]  我是主线程,都给我闪开 : {number = 1, name = main}

2015-09-26 18:26:53.608 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 0

2015-09-26 18:26:53.609 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 1

2015-09-26 18:26:53.609 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 2

2015-09-26 18:26:53.609 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 3

2015-09-26 18:26:53.609 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 0

2015-09-26 18:26:53.609 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 4

2015-09-26 18:26:53.610 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 5

2015-09-26 18:26:53.610 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 1

2015-09-26 18:26:53.610 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 6

2015-09-26 18:26:53.629 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 7

2015-09-26 18:26:53.629 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 2

2015-09-26 18:26:53.630 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 8

2015-09-26 18:26:53.630 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 3

2015-09-26 18:26:53.630 test[2972:151236] 当前线程:{number = 2, name = (null)}  ===== 第二个任务 9

2015-09-26 18:26:53.631 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 4

2015-09-26 18:26:53.631 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 5

2015-09-26 18:26:53.631 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 6

2015-09-26 18:26:53.631 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 7

2015-09-26 18:26:53.672 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 8

2015-09-26 18:26:53.672 test[2972:151235] 当前线程:{number = 3, name = (null)}  ————   第一个任务 9

2015-09-26 18:26:53.795 test[2972:151166] 任务执行完毕...

可见第一个任务运行在线程:NSThread: 0x7feea87012e0 上,第二个任务运行在线程:NSThread: 0x7feea8629240上,相对于第一种方案(运行在一个线程上),效率肯定是高的!

这里先分清两个概念:Queue 和 Async、Sync。

Queue(队列):队列分为串行和并行。

串行队列上面你按照A、B、C、D的顺序添加四个任务,这四个任务按顺序执行,结束顺序也肯定是A、B、C、D。而并行队列上面这四个任务同时执行,完成的顺序是随机的,每次都可能不一样。

Async VS Sync(异步执行和同步执行):使用dispatch_async 调用一个block,这个block会被放到指定的queue队尾等待执行,至于这个block是并行还是串行执行只和dispatch_async参数里面指定的queue是并行和串行有关。但是dispatch_async会马上返回。

使用dispatch_sync 同样也是把block放到指定的queue上面执行,但是会等待这个block执行完毕才会返回,阻塞当前queue直到sync函数返回。

所以队列是串行、并行 和 同步、异步执行调用block是两个完全不一样的概念。

这两个概念清楚了之后就知道为什么死锁了。分两种情况:1、当前queue是串行队列。当前queue上调用sync函数,并且sync函数中指定的queue也是当前queue。需要执行的block被放到当前queue的队尾等待执行,因为这是一个串行的queue,调用sync函数会阻塞当前队列,等待block执行 ; 这个block永远没有机会执行 ; sync函数不返回,所以当前队列就永远被阻塞了,这就造成了死锁。(这就是问题中在主线程调用sync函数,并且在sync函数中传入main_queue作为queue造成死锁的情况)。2、当前queue是并行队列。在并行的queue上面调用sync函数,同时传入当前queue作为参数,并不会造成死锁,因为block会马上被执行,所以sync函数也不会一直等待不返回造成死锁。但是在并行队列上调用sync函数传入当前队列作为参数的用法,想不出什么情况下才会这样用。stackoverflow上面有一个针对这种情况的讨论。

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

推荐阅读更多精彩内容