什么是GCD?
Grand Central Dispatch或者GCD,是⼀一套低层API,提供了⼀一种新的⽅方法来进⾏行并发程序编写。从基本功能上讲,GCD有点像NSOperationQueue,他们都容许程序将 编程
任务切分为多个单⼀一任务而后提交⾄至⼯工做队列来并发地或者串⾏行地执⾏行。GCD⽐比之NSOpertionQueue更底层更⾼高效,而且它不是Cocoa框架的⼀一部分。 除了代码的平⾏行执⾏行能⼒力,GCD还提供⾼高度集成的事件控制系统。能够设置句柄来响应⽂文件描述符、mach ports(Mach port ⽤用于 OS X上的进程间通信)、进程、计时 安全
器、信号、⽤用户⽣生成事件。这些句柄经过GCD来并发执⾏行。 GCD的API很⼤大程度上基于block,固然,GCD也能够脱离block来使⽤用,⽐好比使⽤用传统c机制提供函数指针和上下⽂文指针。实践证实,当配合block使⽤用时,GCD⾮很是简 多线程
单易⽤用且能发挥其最⼤大能⼒力。
你能够在Mac上敲命令“man dispatch”来获取GCD的⽂文档。 为什么使⽤用?
GCD提供不少超越传统多线程编程的优点: 并发
易⽤用: GCD⽐比之thread跟简单易⽤用。因为GCD基于work unit⽽而⾮非像thread那样基于运算,因此GCD能够控制诸如等待任务结束、监视⽂文件描述符、周期执⾏行代码以及 ⼯工做挂起等任务。基于block的⾎血统致使它能极为简单得在不一样代码做⽤用域之间传递上下⽂文。 框架
效率: GCD被实现得如此轻量和优雅,使得它在不少地⽅方⽐比之专⻔门建立消耗资源的线程更实⽤用且快速。这关系到易⽤用性:致使GCD易⽤用的缘由有⼀一部分在于你能够不 ⽤用担⼼心太多的效率问题⽽而仅仅使⽤用它就⾏行了。 异步
性能: GCD⾃自动根据系统负载来增减线程数量,这就减小了上下⽂文切换以及增长了计算效率。 async
Dispatch Objects 函数
尽管GCD是纯c语⾔言的,但它被组建成⾯面向对象的⻛风格。GCD对象被称为dispatch object。Dispatch object像Cocoa对象⼀同样是引⽤用计数的。使⽤用dispatch_release和 dispatch_retain函数来操做dispatch object的引⽤用计数来进⾏行内存管理。但注意不像Cocoa对象,dispatch object并不参与垃圾回收系统,因此即便开启了GC,你也必须⼿手 动管理GCD对象的内存。 性能
Dispatch queues 和 dispatch sources(后⾯面会介绍到)能够被挂起和恢复,能够有⼀一个相关联的任意上下⽂文指针,能够有⼀一个相关联的任务完成触发函数。能够查 阅“man dispatch_object”来获取这些功能的更多信息。 spa
Dispatch Queues
GCD的基本概念就是dispatch queue。dispatch queue是⼀一个对象,它能够接受任务,并将任务以先到先执⾏行的顺序来执⾏行。dispatch queue能够是并发的或串⾏行的。 并发任务会像NSOperationQueue那样基于系统负载来合适地并发进⾏行,串⾏行队列同⼀一时间只执⾏行单⼀一任务。
GCD中有三种队列类型:
The main queue: 与主线程功能相同。实际上,提交⾄至main queue的任务会在主线程中执⾏行。main queue能够调⽤用dispatch_get_main_queue()来得到。由于main queue是与主线程相关的,因此这是⼀一个串⾏行队列。
Global queues: 全局队列是并发队列,并由整个进程共享。进程中存在三个全局队列:⾼高、中(默认)、低、后台四个优先级队列。能够调⽤用 dispatch_get_global_queue函数传⼊入优先级来访问队列。优先级:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
⽤户队列: 用户队列 (GCD并不这样称呼这种队列, 可是没有⼀一个特定的名字来形容这种队列,因此咱们称其为⽤用户队列) 是⽤用函数 dispatch_queue_create 建立的 队列. 这些队列是串⾏行的。正由于如此,它们能够⽤用来完成同步机制, 有点像传统线程中的mutex。
建立队列
要使用户队列,咱们⾸首先得建立⼀一个。调⽤用函数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,在外层中执⾏行后台任务,在内层中将任务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_async 函数会⽴当即返回, block会在后台异步执⾏行。 固然,一般,任务完成时简单地NSLog个消息不是个事⼉儿。在典型的Cocoa程序中,你颇有可能但愿在任务完成时更新界⾯面,这就意味着须要在主线程中执⾏行⼀一些代
者 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途径有⼏几个好处:
1. 平行计算: 注意在第⼆二个版本的代码中, -setSomething:是怎么使⽤用dispatch_async的。调⽤用 -setSomething:会⽴当即返回,而后这⼀一⼤大堆⼯工做会在后台执 ⾏行。若是updateSomethingCaches是⼀一个很费时费⼒力的任务,且调⽤用者将要进⾏行⼀一项处理器⾼高负荷任务,那么这样作会很棒。
2. 安全: 使⽤用GCD,咱们就不可能意外写出具备不成对Lock的代码。在常规Lock代码中,咱们极可能在解锁以前让代码返回了。使⽤用GCD,队列一般持续运⾏行,你必将 归还控制权。
3. 控制: 使⽤用GCD咱们能够挂起和恢复dispatch queue,⽽而这是基于锁的⽅方法所不能实现的。咱们还能够将⼀一个⽤用户队列指向另⼀一个dspatch queue,使得这个⽤用户队列 继承那个dispatch queue的属性。使⽤用这种⽅方法,队列的优先级能够被调整——经过将该队列指向⼀一个不一样的全局队列,如有必要的话,这个队列甚⾄至能够被⽤用来在 主线程上执⾏行代码。
4. 集成: GCD的事件系统与dispatch queue相集成。对象须要使⽤用的任何事件或者计时器均可以从该对象的队列中指向,使得这些句柄能够⾃自动在该队列上执⾏行,从⽽而使 得句柄能够与对象⾃自动同步。
总结
如今你已经知道了GCD的基本概念、怎样建立dispatch queue、怎样提交Job⾄至dispatch queue以及怎样将队列⽤用做线程同步。接下来我会向你展⽰示如何使⽤用GCD来编 写平⾏行执⾏行代码来充分利⽤用多核系统的性能^ ^。我还会讨论GCD更深层的东⻄西,包括事件系统和queue targeting。