GCD小结

本文总结于 一些 iOS / Web 开发相关的翻译或原创博客文章

GCD (grand_central_dispatch)是 libdispatch 的市场名称,而 libdispatch 作为 Apple 的一个库,为并发代码在多核硬件(跑 iOS 或 OS X )上执行提供有力支持。它具有以下优点:

  • GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。
  • GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
  • GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。

Queues 队列

GCD 提供有 dispatch queues 来处理代码块,这些队列管理你提供给 GCD 的任务并用 FIFO 顺序执行这些任务。这就保证了第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。

所有的调度队列(dispatch queues)自身都是线程安全的,你能从多个线程并行的访问它们。当你了解了调度队列如何为你自己代码的不同部分提供线程安全后,GCD的优点就是显而易见的。关于这一点的关键是选择正确类型的调度队列和正确的调度函数来提交你的工作。

  1. GCD 提供了主队列(main queue)全局调度队列(Global Dispatch Queues)
  2. dispatch_get_main_queue() 可以获取到主队列,dispatch_get_global_queue(<#long identifier#>, <#unsigned long flags#>)可以获取对应的全局队列(第一个参数对应可传入 DISPATCH_QUEUE_PRIORITY_HIGH,DISPATCH_QUEUE_PRIORITY_DEFAULT,DISPATCH_QUEUE_PRIORITY_LOW,DISPATCH_QUEUE_PRIORITY_BACKGROUND;第二个参数可传入 0)
  3. 可以通过dispatch_queue_create(<#const char *label#>, <#dispatch_queue_attr_t attr#>)方法来创建自己需要的任务队列(第一个参数是反向DNS样式命名惯例;确保它是描述性的,将有助于调试。第二个参数指定你的队列是串行还是并发,包括DISPATCH_QUEUE_SERIAL,DISPATCH_QUEUE_CONCURRENT

常用方法

dispatch_async 处理后台任务

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ 
    //这里处理可能会造成主线程阻塞的操作(如耗时操作)
    dispatch_async(dispatch_get_main_queue(), ^{
        //这里 返回到主线程 通知主线程刷新 UI 等
    });
});

注意事项

  1. 自定义串行队列:当你想串行执行后台任务并追踪它时就是一个好选择。这消除了资源争用,因为你知道一次只有一个任务在执行。注意若你需要来自某个方法的数据,你必须内联另一个 Block 来找回它或考虑使用 dispatch_sync。
  2. 主队列(串行):这是在一个并发队列上完成任务后更新 UI 的共同选择。要这样做,你将在一个 Block 内部编写另一个 Block 。以及,如果你在主队列调用 dispatch_async 到主队列,你能确保这个新任务将在当前方法完成后的某个时间执行。
  3. 并发队列:这是在后台执行非 UI 工作的共同选择。

dispatch_sync 提交串行任务

注意事项

  1. 自定义串行队列:在这个状况下要非常小心!如果你正运行在一个队列并调用 dispatch_sync 放在同一个队列,那你就百分百地创建了一个死锁。
  2. 主队列(串行):同上面的理由一样,必须非常小心!这个状况同样有潜在的导致死锁的情况。
  3. 并发队列:这才是做同步工作的好选择,不论是通过调度障碍,或者需要等待一个任务完成才能执行进一步处理的情况。

dispatch_after 延后工作

double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); 
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){  
    
});

或者 (系统提供的现有框架)

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
});

注意事项

  1. 自定义串行队列:在一个自定义串行队列上使用 dispatch_after 要小心。你最好坚持使用主队列。
  2. 主队列(串行):是使用 dispatch_after 的好选择;Xcode 提供了一个不错的自动完成模版。
  3. 并发队列:在并发队列上使用 dispatch_after 也要小心;你会这样做就比较罕见。还是在主队列做这些操作吧。

dispatch_once()

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    //通常用来保证 只执行一次此block中的代码,可用在单例中
});

dispatch barriers 处理读者写者问题

dispatch_barrier_async(self.concurrentQueue, ^{
    //添加 写操作到你的自定义队列中 当临界区在稍后执行时,这将是你队列中唯一执行的条目
    [NSMutableArr addObject:model];
    dispatch_async(dispatch_get_main_queue(), ^{ 
        //在这里可以通知主线程 你已经完成了相关操作
    });
});

处理读取部分的代码

__block NSArray = nil;
dispatch_sync(self.concurrentQueue, ^{ 
    //在你自定义的 并发 队列中,进行读取得操作
    array = [NSArray arrayWithArray:_photosArray]; 
});

注意事项

  1. 自定义串行队列:一个很坏的选择;障碍不会有任何帮助,因为不管怎样,一个串行队列一次都只执行一个操作。
  2. 全局并发队列:要小心;这可能不是最好的主意,因为其它系统可能在使用队列而且你不能垄断它们只为你自己的目的。
  3. 自定义并发队列:这对于原子或临界区代码来说是极佳的选择。任何你在设置或实例化的需要线程安全的事物都是使用障碍的最佳候选

Dispatch Groups(调度组)

Dispatch Group 会在整个组的任务都完成时通知你。这些任务可以是同步的,也可以是异步的,即便在不同的队列也行。而且在整个组的任务都完成时,Dispatch Group 可以用同步的或者异步的方式通知你。因为要监控的任务在不同队列,那就用一个 dispatch_group_t 的实例来记下这些不同的任务。

当组中所有的事件都完成时,GCD 的 API 提供了两种通知方式。

第一种是 dispatch_group_wait ,它会阻塞当前线程,直到组里面所有的任务都完成或者等到某个超时发生。这恰好是你目前所需要的。

  1. 自定义串行队列:它很适合当一组任务完成时发出通知。
  2. 主队列(串行):它也很适合这样的情况。但如果你要同步地等待所有工作地完成,那你就不应该使用它,因为你不能阻塞主线程。然而,异步模型是一个很有吸引力的能用于在几个较长任务(例如网络调用)完成后更新 UI 的方式。
  3. 并发队列:它也很适合 Dispatch Group 和完成时通知。

上面的一切都很好,但在另一个队列上异步调度然后使用 dispatch_group_wait 来阻塞实在显得有些笨拙。是的,还有另一种方式……

dispatch_group_notify 以异步的方式工作。当 Dispatch Group 中没有任何任务时,它就会执行其代码,那么 completionBlock 便会运行。你还指定了运行 completionBlock 的队列,此处,主队列就是你所需要的。

对于这个特定的工作,上面的处理明显更清晰,而且也不会阻塞任何线程。

你可能感兴趣的:(GCD小结)