GCD 的理解

Grand Central Dispatch (opens new window) (GCD or libdispatch) 为多核上的并行程序执行上的提供了全面支持。GCD 的出现就是系统帮助开发者管理了一些线程方面的工作让开发者将重心放入自己的业务。减轻了开发者的负担,同时也保证了并行执行的高效。

# GCD 中线程和队列的关系

首先要明确一下这俩完全不一样的东西,GCD 的线程管理是系统帮助维护了一个线程池,系统自己会去根据当前负载去决定调用线程池的哪个线程去执行对应队列里的 block 任务。

对于 GCD 本身而言,GCD 本身是需要关注线程的管理,开发者使用 GCD 的时候,可以不那么关心线程的知识,省去自己创建线程,管理线程等一系列繁琐的事情,而是专心使用 GCD 提供的队列的功能。

之前看微博上有 iOS 开发小集的知识点,有一点是说如何「判断是否在主队列运行」。当时特别好奇什么情况下会用到这种判断呢?后来在一个 issue 里发现了这种判断的使用场景。

说明这个场景之前问个问题,主线程和主队列是什么关系?答案:主队列一定位于主线程,但是主线程可能包含别的队列。可以写个 demo 测试一下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"hello world");
        });
    }
    return 0;
}

运行结果如图, Jietu20171221-160748

而在主线程的主队列里执行的话断点应该是这样的 Jietu20171221-160859

好,明确了这一点后我们继续说一下什么场景会用到判断当前是否在主队列?即当执行一些 UI 操作的时候我们不仅仅需要判断是否是主线程,还要判断是否是主队列。

诶?难道 UI 操作放到主线程还不够安全吗?是的。 相关问题:

  • http://www.openradar.me/24025596
  • https://github.com/ReactiveCocoa/ReactiveCocoa/issues/2635#issuecomment-170215083 Jietu20171221-163735

我们之前被 Apple 教育:UI 操作要放到主线程中执行,但是现在发现放到主线程中执行的时候,因为 Apple 的不靠谱,我们只能自己动手去做个判断。判断主线程的方式就和 iOS 知识小集里的说的一样使用如下代码,通过 key/value 的方式来设置和获取指定的 queue.

BOOL RCTIsMainQueue()
{
  static void *mainQueueKey = &mainQueueKey;
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    dispatch_queue_set_specific(dispatch_get_main_queue(),
                                mainQueueKey, mainQueueKey, NULL);
  });
  return dispatch_get_specific(mainQueueKey) == mainQueueKey;
}

补充一点:上面有个 demo 没有说明的是 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ xxx }); } 这个方法为什么会在主线程执行呢?可以看下 dispatch_sync 的官方 API 说明

As an optimization, this function invokes the block on the current thread when possible.

# GCD 的 API 设计

GCD 里的 API 设计打都是为了保证多线程下任务的执行顺序。GCD 队列(FIFO)这个概念本身也是为了连续执行 block 而生。这里不打算详细说明具体 API 的使用方法。比如 dispatch_group 相关的 API 是封装了一组任务保证该组任务执行完之后执行某个特定任务,dispatch_semaphore 相关的 API 是等某个任务执行完了再执行某个别的任务。特别说明一下 dispatch_semaphore 这个 API 非常好用,我们经常在业务中比如执行某个网络业务的时候,顺序执行 N 个网络请求来保证业务执行成功,我们会在一个网络请求的异步回调里继续调用另外一个请求的方法,通常的做法是不断的嵌套调用函数,这样是能解决问题,但是不够优雅。但是有了 dispatch_semaphore 我们就可以新的方式去执行这种操作,上个 demo 模拟一下顺序执行一系列异步操作的方法:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_semaphore_t sem = dispatch_semaphore_create(0);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"first block");
            dispatch_semaphore_signal(sem);
        });
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            NSLog(@"second block");
            dispatch_semaphore_signal(sem);
        });
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        NSLog(@"this is end");
    }
    return 0;
}

还有 dispatch_barrier_async 控制队列中哪些 block 先执行,哪些后执行。在某种程度上和 dispatch_group 挺像。还有一些是通过控制 queue 的优先级来控制任务的执行顺序的,参考 dispatch_set_target_queue 相关用法。

以上都是我们经常用的 gcd 的方法,通过 GCD 提供的 API 来控制队列中 block 的执行。当然 GCD 还提供了很多别的功能,比如通过 dispatch_source_create 创建定时器等等这里就不列举了。

总结:理清楚线程,队列还有 block 三者的关系就能对 GCD 的使用游刃有余。

参考地址

  • https://developer.apple.com/documentation/dispatch
  • http://blog.krzyzanowskim.com/2016/06/03/queues-are-not-bound-to-any-specific-thread/
  • http://blog.benjamin-encz.de/post/main-queue-vs-main-thread/
  • https://github.com/southpeak/iOS-tech-set/blob/master/2017/10.md#判断是否在主队列运行