GCD介绍(二): 多核心的性能

概念 编程

为了在单一进程中充分发挥多核的优点,咱们有必要使用多线程技术(咱们不必去提多进程,这玩意儿和GCD不要紧)。在低层,GCD全局dispatch queue仅仅是工做线程池的抽象。这些队列中的Block一旦可用,就会被dispatch到工做线程中。提交至用户队列的Block最终也会经过全局队列进入相同的工做线程池(除非你的用户队列的目标是主线程,可是为了提升运行速度,咱们毫不会这么干)。 数组

有两种途径来经过GCD“榨取”多核心系统的性能:将单一任务或者一组相关任务并发至全局队列中运算;将多个不相关的任务或者关联不紧密的任务并发至用户队列中运算; 安全

全局队列 多线程

设想下面的循环: 并发

for(id obj in array)
        [self doSomethingIntensiveWith:obj];

假定 -doSomethingIntensiveWith: 是线程安全的且能够同时执行多个.一个array一般包含多个元素,这样的话,咱们能够很简单地使用GCD来平行运算: app

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for(id obj in array)
        dispatch_async(queue, ^{
            [self doSomethingIntensiveWith:obj];
        });

如此简单,咱们已经在多核心上运行这段代码了。  异步

固然这段代码并不完美。有时候咱们有一段代码要像这样操做一个数组,可是在操做完成后,咱们还须要对操做结果进行其余操做: socket

for(id obj in array)
        [self doSomethingIntensiveWith:obj];
    [self doSomethingWith:array];

这时候使用GCD的 dispatch_async 就悲剧了.咱们还不能简单地使用dispatch_sync来解决这个问题, 由于这将致使每一个迭代器阻塞,就彻底破坏了平行计算。 async

解决这个问题的一种方法是使用dispatch group。一个dispatch group能够用来将多个block组成一组以监测这些Block所有完成或者等待所有完成时发出的消息。使用函数dispatch_group_create来建立,而后使用函数dispatch_group_async来将block提交至一个dispatch queue,同时将它们添加至一个组。因此咱们如今能够从新编码: 函数

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在组完成后执行:

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屡次,并平行运算,而后等待全部运算结束,就像咱们想要的那样:

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函数将全部代码推到后台就好了:

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的监视内部、外部事件的机制。

相关文章
相关标签/搜索