1.不要在GCD中干『持久』的事
GCD 通过创建所谓的线程池来大致匹配 CPU 内核数量。要记住,线程的创建并不是无代价的。每个线程都需要占用内存和内核资源。这里也有一个问题:如果你提交了一个 block 给 GCD,但是这段代码阻塞了这个线程,那么这个线程在这段时间内就不能用来完成其他工作——它被阻塞了。为了确保功能点在队列上一直是执行的,GCD 不得不创建一个新的线程,并把它添加到线程池。
如果你的代码阻塞了许多线程,这会带来很大的问题。首先,线程消耗资源,此外,创建线程会变得代价高昂。创建过程需要一些时间。并且在这段时间中,GCD 无法以全速来完成功能点。有不少能够导致线程阻塞的情况,但是最常见的情况与 I/O 有关,也就是从文件或者网络中读写数据。正是因为这些原因,你不应该在GCD队列中以阻塞的方式来做这些操作。看一下下面的输入输出段落去了解一些关于如何以 GCD 运行良好的方式来做 I/O 操作的信息。
2.单一资源的多读单写
我们能够改善上面的那个例子。GCD 有可以让多线程运行的并发队列。我们能够安全地使用多线程来从 NSMutableDictionary
中读取只要我们不同时修改它。当我们需要改变这个字典时,我们使用 barrier 来分发这个 block。这样的一个 block 的运行时机是,在它之前所有计划好的 block 完成之后,并且在所有它后面的 block 运行之前。
以如下方式创建队列:
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
并且用以下代码来改变setter函数:
- (void)setCount:(NSUInteger)count forKey:(NSString *)key { key = [key copy]; dispatch_barrier_async(self.isolationQueue, ^(){ if (count == 0) { [self.counts removeObjectForKey:key]; } else { self.counts[key] = @(count); } }); }
当使用并发队列时,要确保所有的 barrier 调用都是 async 的。如果你使用 dispatch_barrier_sync
,那么你很可能会使你自己(更确切的说是,你的代码)产生死锁。写操作需要 barrier,并且可以是 async 的。
关于dispatch_barrier_async的更多解释,请看这篇我翻译过的GCD的介绍和使用:iOS学习篇之Grand Central Dispatch(GCD)
3.
异步调用不会产生死锁。因此值得我们在任何可能的时候都使用异步分发。我们使用一个异步调用结果 block 的函数,来代替编写一个返回值(必须要用同步)的方法或者函数。这种方式,我们会有更少发生死锁的可能性。
异步调用的副作用就是它们很难调试。