GCD: 基本概念和Dispatch Queue 【转】

什么是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提供不少超越传统多线程编程的优点:async

  1. 易用: GCD比之thread跟简单易用。因为GCD基于work unit而非像thread那样基于运算,因此GCD能够控制诸如等待任务结束、监视文件描述符、周期执行代码以及工做挂起等任务。基于block的血统致使它能极为简单得在不一样代码做用域之间传递上下文。
  2. 效率: GCD被实现得如此轻量和优雅,使得它在不少地方比之专门建立消耗资源的线程更实用且快速。这关系到易用性:致使GCD易用的缘由有一部分在于你能够不用担忧太多的效率问题而仅仅使用它就好了。
  3. 性能: GCD自动根据系统负载来增减线程数量,这就减小了上下文切换以及增长了计算效率。

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中有三种队列类型:

  1. The main queue: 与主线程功能相同。实际上,提交至main queue的任务会在主线程中执行。main queue能够调用dispatch_get_main_queue()来得到。由于main queue是与主线程相关的,因此这是一个串行队列。
  2. Global queues: 全局队列是并发队列,并由整个进程共享。进程中存在三个全局队列:高、中(默认)、低三个优先级队列。能够调用dispatch_get_global_queue函数传入优先级来访问队列。
  3. 用户队列: 用户队列 (GCD并不这样称呼这种队列, 可是没有一个特定的名字来形容这种队列,因此咱们称其为用户队列) 是用函数 dispatch_queue_create 建立的队列. 这些队列是串行的。正由于如此,它们能够用来完成同步机制, 有点像传统线程中的mutex。

建立队列

要使用用户队列,咱们首先得建立一个。调用函数dispatch_queue_create就好了。函数的第一个参数是一个标签,这纯是为了 debug。Apple建议咱们使用倒置域名来命名队列,好比“com.dreamingwish.subsystem.task”。这些名字会在崩溃日 志中被显示出来,也能够被调试器调用,这在调试中会颇有用。第二个参数目前还不支持,传入NULL就好了。

提交 Job

向一个队列提交Job很简单:调用dispatch_async函数,传入一个队列和一个block。队列会在轮到这个block执行时执行这个block的代码。下面的例子是一个在后台执行一个巨长的任务:

 
 
 
 
 
  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
  2.         [self goDoSomethingLongAndInvolved]; 
  3.         NSLog(@"Done doing something long and involved"); 
  4. }); 

dispatch_async 函数会当即返回, block会在后台异步执行。 

固然,一般,任务完成时简单地NSLog个消息不是个事儿。在典型的Cocoa程序中,你颇有可能但愿在任务完成时更新界面,这就意味着须要在主线 程中执行一些代码。你能够简单地完成这个任务——使用嵌套的dispatch,在外层中执行后台任务,在内层中将任务dispatch到main queue:

 
 
 
 
 
  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
  2.         [self goDoSomethingLongAndInvolved]; 
  3.         dispatch_async(dispatch_get_main_queue(), ^{ 
  4.             [textField setStringValue:@"Done doing something long and involved"]; 
  5.         }); 
  6. }); 

还有一个函数叫dispatch_sync,它干的事儿和dispatch_async相同,可是它会等待block中的代码执行完成并返回。结合 __block类型修饰符,能够用来从执行中的block获取一个值。例如,你可能有一段代码在后台执行,而它须要从界面控制层获取一个值。那么你可使 用dispatch_sync简单办到:

 
 
 
 
 
  1. __block NSString *stringValue; 
  2. dispatch_sync(dispatch_get_main_queue(), ^{ 
  3.         // __block variables aren't automatically retained 
  4.         // so we'd better make sure we have a reference we can keep 
  5.         stringValue = [[textField stringValue] copy]; 
  6. }); 
  7. [stringValue autorelease]; 
  8. // use stringValue in the background now 

咱们还可使用更好的方法来完成这件事——使用更“异步”的风格。不一样于取界面层的值时要阻塞后台线程,你可使用嵌套的block来停止后台线程,而后从主线程中获取值,而后再将后期处理提交至后台线程:

 
 
 
 
 
  1. dispatch_queue_t bgQueue = myQueue; 
  2.    dispatch_async(dispatch_get_main_queue(), ^{ 
  3.        NSString *stringValue = [[[textField stringValue] copy] autorelease]; 
  4.        dispatch_async(bgQueue, ^{ 
  5.            // use stringValue in the background now 
  6.        }); 
  7.    }); 

取决于你的需求,myQueue能够是用户队列也可使全局队列。

再也不使用锁(Lock)

用户队列能够用于替代锁来完成同步机制。在传统多线程编程中,你可能有一个对象要被多个线程使用,你须要一个锁来保护这个对象:

 
 
 
 
 
  1. NSLock *lock; 

访问代码会像这样:

 
 
 
 
 
  1. - (id)something 
  2.    { 
  3.        id localSomething; 
  4.        [lock lock]; 
  5.        localSomething = [[something retain] autorelease]; 
  6.        [lock unlock]; 
  7.        return localSomething; 
  8.    } 
  9.  
  10.    - (void)setSomething:(id)newSomething 
  11.    { 
  12.        [lock lock]; 
  13.        if(newSomething != something) 
  14.        { 
  15.            [something release]; 
  16.            something = [newSomething retain]; 
  17.            [self updateSomethingCaches]; 
  18.        } 
  19.        [lock unlock]; 
  20.    } 

使用GCD,可使用queue来替代:

 
 
 
 
 
  1. dispatch_queue_t queue; 

要用于同步机制,queue必须是一个用户队列,而非全局队列,因此使用usingdispatch_queue_create初始化一个。而后能够用dispatch_async 或者 dispatch_sync将共享数据的访问代码封装起来:

 
 
 
 
 
  1. - (id)something 
  2.     __block id localSomething; 
  3.     dispatch_sync(queue, ^{ 
  4.         localSomething = [something retain]; 
  5.     }); 
  6.     return [localSomething autorelease]; 
  7.  
  8. - (void)setSomething:(id)newSomething 
  9.     dispatch_async(queue, ^{ 
  10.         if(newSomething != something) 
  11.         { 
  12.             [something release]; 
  13.             something = [newSomething retain]; 
  14.             [self updateSomethingCaches]; 
  15.         } 
  16.     }); 

值得注意的是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。

相关文章
相关标签/搜索