概念:node
程序中同步和异步是什么意思?有什么区别?编程
解释一:
串行执行至关于同步数组
并发执行至关于异步缓存
GCD介绍(一): 基本概念和Dispatch Queue安全
GCD提供不少超越传统多线程编程的优点:服务器
Dispatch Objects多线程
GCD对象被称为dispatch object。Dispatch object像Cocoa对象同样是引用计数的。使用dispatch_release和dispatch_retain函数来操做dispatch object的引用计数来进行内存管理。并发
但注意不像Cocoa对象,dispatch object并不参与垃圾回收系统,因此即便开启了ARC,你也必须手动管理GCD对象的内存。app
Dispatch queues 和 dispatch sources(后面会介绍到)能够被挂起和恢复,能够有一个相关联的任意上下文指针,能够有一个相关联的任务完成触发函数。异步
Dispatch Queues
GCD的基本概念就是dispatch queue。dispatch queue是一个对象,它能够接受任务,并将任务以先到先执行的顺序来执行。dispatch queue能够是并发的或串行的。并发任务会像NSOperationQueue那样基于系统负载来合适地并发进行,串行队列同一时间只执行单一任务。
GCD中有三种队列类型:
建立队列
要使用用户队列,咱们首先得建立一个。调用函数dispatch_queue_create 就好了。函数的第一个参数是一个标签,这纯是为了debug。Apple建议咱们使用倒置域名来命名队列,好比 “com.dreamingwish.subsystem.task”。这些名字会在崩溃日志中被显示出来,也能够被调试器调用,这在调试中会颇有用。第 二个参数目前还不支持,传入NULL就好了。
提交 Job
向一个队列提交Job很简单:调用dispatch_async函数,传入一个队列和一个block。队列会在轮到这个block执行时执行这个block的代码。下面的例子是一个在后台执行一个巨长的任务:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self goDoSomethingLongAndInvolved];
NSLog(@"Done doing something long and involved");
});
dispatch_async 函数会当即返回, block会在后台异步执行。
固然,一般,任务完成时简单地NSLog个消息不是个事儿。在典型的Cocoa程序中,你很 有可能但愿在任务完成时更新界面,这就意味着须要在主线 程中执行一些代码。你能够简单地完成这个任务——使用嵌套的dispatch,在外层中执行后台任务,在内层中将任务dispatch到main queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self goDoSomethingLongAndInvolved];
dispatch_async(dispatch_get_main_queue(), ^{
[textField setStringValue:@"Done doing something long and involved"];
});
});
还有一个函数叫dispatch_sync,它干的事儿和dispatch_async相 同,可是它会等待block中的代码执行完成并返回。结合 __block类型修饰符,能够用来从执行中的block获取一个值。例如,你可能有一段代码在后台执行,而它须要从界面控制层获取一个值。那么你可使 用dispatch_sync简单办到:
__block NSString *stringValue;
dispatch_sync(dispatch_get_main_queue(), ^{
// __block variables aren't automatically retained
// so we'd better make sure we have a reference we can keep
stringValue = [[textField stringValue] copy];
});
[stringValue autorelease];
// use stringValue in the background now
咱们还可使用更好的方法来完成这件事——使用更“异步”的风格。不一样于取界面层的值时要阻塞后台线程,你可使用嵌套的block来停止后台线程,而后从主线程中获取值,而后再将后期处理提交至后台线程:
dispatch_queue_t bgQueue = myQueue;
dispatch_async(dispatch_get_main_queue(), ^{
NSString *stringValue = [[[textField stringValue] copy] autorelease];
dispatch_async(bgQueue, ^{
// use stringValue in the background now
});
});
取决于你的需求,myQueue能够是用户队列也可使全局队列。
再也不使用锁(Lock)
用户队列能够用于替代锁来完成同步机制。在传统多线程编程中,你可能有一个对象要被多个线程使用,你须要一个锁来保护这个对象:
NSLock *lock;
访问代码会像这样:
- (id)something
{
id localSomething;
[lock lock];
localSomething = [[something retain] autorelease];
[lock unlock];
return localSomething;
}
- (void)setSomething:(id)newSomething
{
[lock lock];
if(newSomething != something)
{
[something release];
something = [newSomething retain];
[self updateSomethingCaches];
}
[lock unlock];
}
使用GCD,可使用queue来替代:
dispatch_queue_t queue;
要用于同步机制,queue必须是一个用户队列,而非全局队列,因此使用usingdispatch_queue_create初始化一个。而后能够用dispatch_async 或者 dispatch_sync将共享数据的访问代码封装起来:
- (id)something
{
__block id localSomething;
dispatch_sync(queue, ^{
localSomething = [something retain];
});
return [localSomething autorelease];
}
- (void)setSomething:(id)newSomething
{
dispatch_async(queue, ^{
if(newSomething != something)
{
[something release];
something = [newSomething retain];
[self updateSomethingCaches];
}
});
}
值得注意的是dispatch queue是很是轻量级的,因此你能够大用特用,就像你之前使用lock同样。
如今你可能要问:“这样很好,可是有意思吗?我就是换了点代码办到了同一件事儿。”
实际上,使用GCD途径有几个好处:
总结
如今你已经知道了GCD的基本概念、怎样建立dispatch queue、怎样提交Job至dispatch queue以及怎样将队列用做线程同步。接下来我会向你展现如何使用GCD来编写平行执行代码来充分利用多核系统的性能^ ^。我还会讨论GCD更深层的东西,包括事件系统和queue targeting。
GCD介绍(二): 多核心的性能
概念
为了在单一进程中充分发挥多核的优点,咱们有必要使用多线程技术(咱们不必去提多进程,这 玩意儿和GCD不要紧)。在低层,GCD全局 dispatch queue仅仅是工做线程池的抽象。这些队列中的Block一旦可用,就会被dispatch到工做线程中。提交至用户队列的Block最终也会经过全局 队列进入相同的工做线程池(除非你的用户队列的目标是主线程,可是为了提升运行速度,咱们毫不会这么干)。
有两种途径来经过GCD“榨取”多核心系统的性能:将单一任务或者一组相关任务并发至全局队列中运算;将多个不相关的任务或者关联不紧密的任务并发至用户队列中运算;
全局队列
设想下面的循环:
1 2 |
for(id obj in array) [self doSomethingIntensiveWith:obj]; |
假定 -doSomethingIntensiveWith: 是线程安全的且能够同时执行多个.一个array一般包含多个元素,这样的话,咱们能够很简单地使用GCD来平行运算:
1 2 3 4 5 |
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for(id obj in array) dispatch_async(queue, ^{ [self doSomethingIntensiveWith:obj]; }); |
如此简单,咱们已经在多核心上运行这段代码了。
固然这段代码并不完美。有时候咱们有一段代码要像这样操做一个数组,可是在操做完成后,咱们还须要对操做结果进行其余操做:
1 2 3 |
for(id obj in array) [self doSomethingIntensiveWith:obj]; [self doSomethingWith:array]; |
这时候使用GCD的 dispatch_async 就悲剧了.咱们还不能简单地使用dispatch_sync来解决这个问题, 由于这将致使每一个迭代器阻塞,就彻底破坏了平行计算。
解决这个问题的一种方法是使用dispatch group。一个dispatch group能够用来将多个block组成一组以监测这些Block所有完成或者等待所有完成时发出的消息。使用函数 dispatch_group_create来建立,而后使用函数dispatch_group_async来将block提交至一个dispatch queue,同时将它们添加至一个组。因此咱们如今能够从新代码:
1 2 3 4 5 6 7 8 9 10 |
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); for(id obj in array) dispatch_group_async(group, queue, ^{ [self doSomethingIntensiveWith:obj]; }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group);
[self doSomethingWith:array]; |
若是这些工做能够异步执行,那么咱们能够更风骚一点,将函数-doSomethingWith:放在后台执行。咱们使用dispatch_group_async函数创建一个block在组完成后执行:
1 2 3 4 5 6 7 8 9 10 |
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); for(id obj in array) dispatch_group_async(group, queue, ^{ [self doSomethingIntensiveWith:obj]; }); dispatch_group_notify(group, queue, ^{ [self doSomethingWith:array]; }); dispatch_release(group); |
不只全部数组元素都会被平行操做,后续的操做也会异步执行,而且这些异步运算都会将程序的其 他部分考虑在内。注意若是-doSomethingWith:须要在主线程中执行,好比操做GUI,那么咱们只要将main queue而非全局队列传给dispatch_group_notify函数就好了。
对于同步执行,GCD提供了一个简化方法叫作dispatch_apply。这个函数调用单一block屡次,并平行运算,而后等待全部运算结束,就像咱们想要的那样:
1 2 3 4 5 |
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply([array count], queue, ^(size_t index){ [self doSomethingIntensiveWith:[array objectAtIndex:index]]; }); [self doSomethingWith:array]; |
这很棒,可是异步咋办?dispatch_apply函数但是没有异步版本的。可是咱们使用的但是一个为异步而生的API啊!因此咱们只要用dispatch_async函数将全部代码推到后台就好了:
1 2 3 4 5 6 7 |
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ dispatch_apply([array count], queue, ^(size_t index){ [self doSomethingIntensiveWith:[array objectAtIndex:index]]; }); [self doSomethingWith:array]; }); |
简单的要死!
这种方法的关键在于肯定咱们的代码是在一次对不一样的数据片断进行类似的操做。若是你肯定你的任务是线程安全的(不在本篇讨论范围内)那么你可使用GCD来重写你的循环了,更平行更风骚。
要看到性能提高,你还得进行一大堆工做。比之线程,GCD是轻量和低负载的,可是将 block提交至queue仍是很消耗资源的——block须要 被拷贝和入队,同时适当的工做线程须要被通知。不要将一张图片的每一个像素做为一个block提交至队列,GCD的优势就半途夭折了。若是你不肯定,那么请 进行试验。将程序平行计算化是一种优化措施,在修改代码以前你必须再三思索,肯定修改是有益的(还有确保你修改了正确的地方)。
Subsystem并发运算
前面的章节咱们讨论了在程序的单个subsystem中发挥多核心的优点。下来咱们要跨越多个子系统。
例如,设想一个程序要打开一个包含meta信息的文档。文档数据自己须要解析并转换至模型对 象来显示,meta信息也须要解析和转换。可是,文档数 据和meta信息不须要交互。咱们能够为文档和meta各建立一个dispatch queue,而后并发执行。文档和meta的解析代码都会各自串行执行,从而不用考虑线程安全(只要没有文档和meta之间共享的数据),可是它们仍是并 发执行的。
一旦文档打开了,程序须要响应用户操做。例如,可能须要进行拼写检查、代码高亮、字数统计、 自动保存或者其余什么。若是每一个任务都被实现为在不一样的 dispatch queue中执行,那么这些任务会并发执行,并各自将其余任务的运算考虑在内(respect to each other),从而省去了多线程编程的麻烦。
使用dispatch source(下次我会讲到),咱们可让GCD将事件直接传递给用户队列。例如,程序中监视socket链接的代码能够被置于它本身的dispatch queue中,这样它会异步执行,而且执行时会将程序其余部分的运算考虑在内。另外,若是使用用户队列的话,这个模块会串行执行,简化程序。
结论
咱们讨论了如何使用GCD来提高程序性能以及发挥多核系统的优点。尽管咱们须要比较谨慎地编写并发程序,GCD仍是使得咱们能更简单地发挥系统的可用计算资源。
下一篇中,咱们将讨论dispatch source,也就是GCD的监视内部、外部事件的机制。
GCD介绍(三): Dispatch Sources
何为Dispatch Sources
简单来讲,dispatch source是一个监视某些类型事件的对象。当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中。
说的貌似有点不清不楚。咱们到底讨论哪些事件类型?
下面是GCD 10.6.0版本支持的事件:
这是一堆颇有用的东西,它支持全部kqueue所支持的事件(kqueue是什么?见 http://en.wikipedia.org/wiki/Kqueue)以及mach(mach是什么?见http: //en.wikipedia.org/wiki/Mach_(kernel))端口、内建计时器支持(这样咱们就不用使用超时参数来建立本身的计时器) 和用户事件。
用户事件
这些事件里面多数均可以从名字中看出含义,可是你可能想知道啥叫用户事件。简单地说,这种事件是由你调用dispatch_source_merge_data函数来向本身发出的信号。
这个名字对于一个发出事件信号的函数来讲,太怪异了。这个名字的来由是GCD会在事件句柄被 执行以前自动将多个事件进行联结。你能够将数据“拼接” 至dispatch source中任意次,而且若是dispatch queue在这期间繁忙的话,GCD只会调用该句柄一次(不要以为这样会有问题,看完下面的内容你就明白了)。
用户事件有两 种: DISPATCH_SOURCE_TYPE_DATA_ADD 和 DISPATCH_SOURCE_TYPE_DATA_OR.用户事件源有 个 unsigned long data属性,咱们将一个 unsigned long传入 dispatch_source_merge_data。当使用 _ADD版本时,事件在联结时会把这些数字相加。当使用 _OR版本时, 事件在联结时会把这些数字逻辑与运算。当事件句柄执行时,咱们可使用dispatch_source_get_data函数访问当前值,而后这个值会被 重置为0。
让我假设一种状况。假设一些异步执行的代码会更新一个进度条。由于主线程只不过是GCD的另 一个dispatch queue而已,因此咱们能够将GUI更新工做push到主线程中。然而,这些事件可能会有一大堆,咱们不想对GUI进行频繁而累赘的更新,理想的状况是 当主线程繁忙时将全部的改变联结起来。
用dispatch source就完美了,使用DISPATCH_SOURCE_TYPE_DATA_ADD,咱们能够将工做拼接起来,而后主线程能够知道从上一次处理完事件到如今一共发生了多少改变,而后将这一整段改变一次更新至进度条。
啥也不说了,上代码:
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
[progressIndicator incrementBy:dispatch_source_get_data(source)];
});
dispatch_resume(source);
dispatch_apply([array count], globalQueue, ^(size_t index) {
// do some work on data at index
dispatch_source_merge_data(source, 1);
});
(对于这段代码,我很想说点什么,我第一次用dispatch source时,我纠结了好久,非常崩溃:Dispatch queue启动时默认状态是挂起的,咱们建立完毕以后得主动恢复,不然事件不会被传送)
假设你已经将进度条的min/max值设置好了,那么这段代码就完美了。数据会被并发处理。 当每一段数据完成后,会通知dispatch source并将dispatch source data加1,这样咱们就认为一个单元的工做完成了。事件句柄根据已完成的工做单元来更新进度条。若主线程比较空闲而且这些工做单元进行的比较慢,那么事 件句柄会在每一个工做单元完成的时候被调用,实时更新。若是主线程忙于其余工做,或者工做单元完成速度很快,那么完成事件会被联结起来,致使进度条只在主线 程变得可用时才被更新,而且一次将积累的改变动新至GUI。
如今你可能会想,听起来却是不错,可是要是我不想让事件被联结呢?有时候你可能想让每一次信 号都会引发响应,什么后台的智能玩意儿通通不要。啊。。 其实很简单的,把你的思想放到禁锢的框子以外就好了。若是你想让每个信号都获得响应,那使用dispatch_async函数不就好了。实际上,使用的 dispatch source而不使用dispatch_async的惟一缘由就是利用联结的优点。
内建事件
上面就是怎样使用用户事件,那么内建事件呢?看看下面这个例子,用GCD读取标准输入:
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t stdinSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
STDIN_FILENO,
0,
globalQueue);
dispatch_source_set_event_handler(stdinSource, ^{
char buf[1024];
int len = read(STDIN_FILENO, buf, sizeof(buf));
if(len > 0)
NSLog(@"Got data from stdin: %.*s", len, buf);
});
dispatch_resume(stdinSource);
简单的要死!由于咱们使用的是全局队列,句柄自动在后台执行,与程序的其余部分并行,这意味着对这种状况的提速:事件进入程序时,程序正在处理其余事务。
这是标准的UNIX方式来处理事务的好处,不用去写loop。若是使用经典的 read调 用,咱们还得万分留神,由于返回的数据可能比请求的少,还得忍受无厘头的“errors”,好比 EINTR (终端系统调用)。使用GCD,咱们啥都不 用管,就从这些蛋疼的状况里解脱了。若是咱们在文件描述符中留下了未读取的数据,GCD会再次调用咱们的句柄。
对于标准输入,这没什么问题,可是对于其余文件描述符,咱们必须考虑在完成读写以后怎样清除 描述符。对于dispatch source还处于活跃状态时,咱们决不能关闭描述符。若是另外一个文件描述符被建立了(多是另外一个线程建立的)而且新的描述符恰好被分配了相同的数字, 那么你的dispatch source可能会在不该该的时候忽然进入读写状态。de这个bug可不是什么好玩的事儿。
适当的清除方式是使 用 dispatch_source_set_cancel_handler,并传入一个block来关闭文件描述符。而后咱们使 用 dispatch_source_cancel来取消dispatch source,使得句柄被调用,而后文件描述符被关闭。
使用其余dispatch source类型也差很少。总的来讲,你提供一个source(mach port、文件描述符、进程ID等等)的区分符来做为diapatch source的句柄。mask参数一般不会被使用,可是对于 DISPATCH_SOURCE_TYPE_PROC 来讲mask指的是咱们想要接受哪一 种进程事件。而后咱们提供一个句柄,而后恢复这个source(前面我加粗字体所说的,得先恢复),搞定。dispatch source也提供一个特定于source的data,咱们使用 dispatch_source_get_data函数来访问它。例如,文件描述符会给 出大体可用的字节数。进程source会给出上次调用以后发生的事件的mask。具体每种source给出的data的含义,看man page吧。
计时器
计时器事件稍有不一样。它们不使用handle/mask参数,计时器事件使用另一个函数 dispatch_source_set_timer 来配置计时器。这个函数使用三个参数来控制计时器触发:
start参数控制计时器第一次触发的时刻。参数类型 是 dispatch_time_t,这是一个opaque类型,咱们不能直接操做它。咱们得须要 dispatch_time 和 dispatch_walltime 函数来建立它们。另外,常量 DISPATCH_TIME_NOW 和DISPATCH_TIME_FOREVER 一般颇有用。
interval参数没什么好解释的。
leeway参数比较有意思。这个参数告诉系统咱们须要计时器触发的精准程度。全部的计时 器都不会保证100%精准, 这个参数用来告诉系统你但愿系统保证精准的努力程度。若是你但愿一个计时器没五秒触发一次,而且越准越好,那么你传递0为参数。另外,若是是一个周期性任 务,好比检查email,那么你会但愿每十分钟检查一次,可是不用那么精准。因此你能够传入60,告诉系统60秒的偏差是可接受的。
这样有什么意义呢?简单来讲,就是下降资源消耗。若是系统可让cpu休息足够长的时间,并 在每次醒来的时候执行一个任务集合,而不是不断的醒来睡 去以执行任务,那么系统会更高效。若是传入一个比较大的leeway给你的计时器,意味着你容许系统拖延你的计时器来将计时器任务与其余任务联合起来一块儿 执行。
总结
如今你知道怎样使用GCD的dispatch source功能来监视文件描述符、计时器、联结的用户事件以及其余相似的行为。因为dispatch source彻底与dispatch queue相集成,因此你可使用任意的dispatch queue。你能够将一个dispatch source的句柄在主线程中执行、在全局队列中并发执行、或者在用户队列中串行执行(执行时会将程序的其余模块的运算考虑在内)。
下一篇我会讨论如何对dispatch queue进行挂起、恢复、重定目标操做;如何使用dispatch semaphore;如何使用GCD的一次性初始化功能。
GCD介绍(四): 完结
Dispatch Queue挂起
dispatch queue能够被挂起和恢复。使用 dispatch_suspend函数来挂起,使用 dispatch_resume 函数来恢复。这两个函数的行为是如你所愿的。另外,这两个仍是也能够用于dispatch source。
一个要注意的地方是,dispatch queue的挂起是block粒度的。换句话说,挂起一个queue并不会将当前正在执行的block挂起。它会容许当前执行的block执行完毕,而后后续的block再也不会被执行,直至queue被恢复。
还有一个注意点:从man页上得来的:若是你挂起了一个queue或者source,那么销毁它以前,必须先对其进行恢复。
Dispatch Queue目标指定
全部的用户队列都有一个目标队列概念。从本质上讲,一个用户队列其实是不执行任何任务的,可是它会将任务传递给它的目标队列来执行。一般,目标队列是默认优先级的全局队列。
用户队列的目标队列能够用函数 dispatch_set_target_queue来修 改。咱们能够将任意 dispatch queue传递给这个函数,甚至能够是另外一个用户队列,只要别构成循环就行。这个函数能够用来设定用户队列的优先级。好比咱们能够将用户队列的目标队列设 定为低优先级的全局队列,那么咱们的用户队列中的任务都会以低优先级执行。高优先级也是同样道理。
有一个用途,是将用户队列的目标定为main queue。这会致使全部提交到该用户队列的block在主线程中执行。这样作来替代直接在主线程中执行代码的好处在于,咱们的用户队列能够单独地被挂起 和恢复,还能够被重定目标至一个全局队列,而后全部的block会变成在全局队列上执行(只要你确保你的代码离开主线程不会有问题)。
还有一个用途,是将一个用户队列的目标队列指定为另外一个用户队列。这样作能够强制多个队列相 互协调地串行执行,这样足以构建一组队列,经过挂起和暂 停那个目标队列,咱们能够挂起和暂停整个组。想象这样一个程序:它扫描一组目录而且加载目录中的内容。为了不磁盘竞争,咱们要肯定在同一个物理磁盘上同 时只有一个文件加载任务在执行。而但愿能够同时从不一样的物理磁盘上读取多个文件。要实现这个,咱们要作的就是建立一个dispatch queue结构,该结构为磁盘结构的镜像。
首先,咱们会扫描系统并找到各个磁盘,为每一个磁盘建立一个用户队列。而后扫描文件系统,并为 每一个文件系统建立一个用户队列,将这些用户队列的目标队 列指向合适的磁盘用户队列。最后,每一个目录扫描器有本身的队列,其目标队列指向目录所在的文件系统的队列。目录扫描器枚举本身的目录并为每一个文件向本身的 队列提交一个block。因为整个系统的创建方式,就使得每一个物理磁盘被串行访问,而多个物理磁盘被并行访问。除了队列初始化过程,咱们根本不须要手动干 预什么东西。
信号量
dispatch的信号量是像其余的信号量同样的,若是你熟悉其余多线程系统中的信号量,那么这一节的东西再好理解不过了。
信号量是一个整形值而且具备一个初始计数值,而且支持两个操做:信号通知和等待。当一个信号量被信号通知,其计数会被增长。当一个线程在一个信号量上等待时,线程会被阻塞(若是有必要的话),直至计数器大于零,而后线程会减小这个计数。
咱们使用函数 dispatch_semaphore_create 来建立dispatch信号量,使用函数 dispatch_semaphore_signal 来信号通知,使用函数 dispatch_semaphore_wait 来等待。这些函数的man页有两个很好的例子,展现了怎样使用信号量来同步任务和有限资源访问控制。
单次初始化
GCD还提供单词初始化支持,这个与pthread中的函数 pthread_once 很类似。GCD提供的方式的优势在于它使用block而非函数指针,这就容许更天然的代码方式:
这个特性的主要用途是惰性单例初始化或者其余的线程安全数据共享。典型的单例初始化技术看起来像这样(线程安全的):
+ (id)sharedWhatever
{
static Whatever *whatever = nil;
@synchronized([Whatever class])
{
if(!whatever)
whatever = [[Whatever alloc] init];
}
return whatever;
}
这挺好的,可是代价比较昂贵;每次调用 +sharedWhatever 函数都会付出取锁的代价,即便这个锁只须要进行一次。确实有更风骚的方式来实现这个,使用相似双向锁或者是原子操做的东西,可是这样挺难弄并且容易出错。
使用GCD,咱们能够这样重写上面的方法,使用函数 dispatch_once:
+ (id)sharedWhatever
{
static dispatch_once_t pred;
static Whatever *whatever = nil;
dispatch_once(&pred, ^{
whatever = [[Whatever alloc] init];
});
return whatever;
}
这个稍微比 @synchronized方法简单些,而且GCD确保以更快的方式完成这些检 测,它保证block中的代码在任何线程经过 dispatch_once 调用以前被执行,但它不会强制每次调用这个函数都让代码进行同步控制。实际上,若是你去看这个函数所在的头文件,你会发现 目前它的实现实际上是一个宏,进行了内联的初始化测试,这意味着一般状况下,你不用付出函数调用的负载代价,而且会有更少的同步控制负载。
结论
这一章,咱们介绍了dispatch queue的挂起、恢复和目标重定,以及这些功能的一些用途。另外,咱们还介绍了如何使用dispatch 信号量和单次初始化功能。到此,我已经完成了GCD如何运做以及如何使用的介绍。