求免费的放开我北鼻1 百度云2百度云资源,谢谢!!要是直接发连接更好

1341人阅读
架构面试(25)
只有当要解决的问题需要一定程度的并发性时,程序员才会从多线程应用程序中受益。例如,如果打印队列应用程序仅支持一台打印机和一台客户机,则不应该将它编写为多线程的。一般说来,包含并发性的编码问题通常都包含一些可以并发执行的操作,同时也包含一些不可并发执行的操作。例如,为多个客户机和一个打印机提供服务的打印队列可以支持对打印的并发请求,但向打印机的输出必须是串行形式的。多线程实现还可以改善交互式应用程序的响应时间。
虽然多线程应用程序中的大多数操作都可以并行进行,但也有某些操作(如更新全局标志或处理共享文件)不能并行进行。在这些情况下,必须获得一个锁来防止其他线程在执行此操作的线程完成之前访问同一个方法。在 Java 程序中,这个锁是通过&synchronized&关键字提供的。清单 1 说明了它的用法。
public class MaxScore {
public MaxScore() {
public synchronized void currentScore(int s) {
if(s& max) {
public int max() {
这里,两个线程不能同时调用 currentScore() 方法;当一个线程工作时,另一个线程必须阻塞。但是,可以有任意数量的线程同时通过 max() 方法访问最大值,因为 max() 不是同步方法,因此它与锁定无关。
试考虑在 MaxScore 类中添加另一个方法的影响,该方法的实现如清单 2 所示。
public synchronized void reset() {
这个方法(当被访问时)不仅将阻塞 reset() 方法的其他调用,而且也将阻塞 MaxScore 类的同一个实例中的 currentScore() 方法,因为这两个方法都访问同一个锁。如果两个方法必须不彼此阻塞,则程序员必须在更低的级别使用同步。清单 3 是另一种情况,其中两个同步的方法可能需要彼此独立。
import java.util.*;
public class Jury {
public Jury() {
members = new Vector(12, 1);
alternates = new Vector(12, 1);
public synchronized void addMember(String name) {
members.add(name);
public synchronized void addAlt(String name) {
alternates.add(name);
public synchronized Vector all() {
Vector retval = new Vector(members);
retval.addAll(alternates);
此处,两个不同的线程可以将 members 和 alternates 添加到 Jury 对象中。请记住,&synchronized&关键字既可用于方法,更一般地,也可用于任何代码块。清单 4 中的两段代码是等效的。
synchronized void f() {
void f() {
// 执行某些操作
synchronized(this) {
// 执行某些操作
所以,为了确保 addMember() 和 addAlt() 方法不彼此阻塞,可按清单 5 所示重写 Jury 类。
import java.util.*;
public class Jury {
public Jury() {
members = new Vector(12, 1);
alternates = new Vector(12, 1);
public void addMember(String name) {
synchronized(members) {
members.add(name);
public void addAlt(String name) {
synchronized(alternates) {
alternates.add(name);
public Vector all() {
synchronized(members) {
retval = new Vector(members);
synchronized(alternates) {
retval.addAll(alternates);
请注意,我们还必须修改 all() 方法,因为对 Jury 对象同步已没有意义。在改写后的版本中,addMember()、addAlt() 和 all() 方法只访问与 members 和 alternates 对象相关的锁,因此锁定 Jury 对象毫无用处。另请注意,all() 方法本来可以写为清单 6 所示的形式。
public Vector all() {
synchronized(members) {
synchronized(alternates) {
retval = new Vector(members);
retval.addAll(alternates);
但是,因为我们早在需要之前就获得 members 和 alternates 的锁,所以这效率不高。清单 5 中的改写形式是一个较好的示例,因为它只在最短的时间内持有锁,并且每次只获得一个锁。这样就完全避免了当以后增加代码时可能产生的潜在死锁问题。
正如在前面看到的那样,同步方法获取对象的一个锁。如果该方法由不同的线程频繁调用,则此方法将成为瓶颈,因为它会对并行性造成限制,从而会对效率造成限制。这样,作为一个一般的原则,应该尽可能地少用同步方法。尽管有这个原则,但有时一个方法可能需要完成需要锁定一个对象几项任务,同时还要完成相当耗时的其他任务。在这些情况下,可使用一个动态的“锁定-释放-锁定-释放”方法。例如,清单 7 和清单 8 显示了可按这种方式变换的代码。
public synchonized void doWork() {
unsafe1();
write_file();
unsafe2();
public void doWork() {
synchonized(this) {
unsafe1();
write_file();
synchonized(this) {
unsafe2();
清单 7 和清单 8 假定第一个和第三个方法需要对象被锁定,而更耗时的 write_file() 方法不需要对象被锁定。如您所见,重写此方法以后,对此对象的锁在第一个方法完成以后被释放,然后在第三个方法需要时重新获得。这样,当 write_file() 方法执行时,等待此对象的锁的任何其他方法仍然可以运行。将同步方法分解为这种混合代码可以明显改善性能。但是,您需要注意不要在这种代码中引入逻辑错误。
内部类在 Java 程序中实现了一个令人关注的概念,它允许将整个类嵌套在另一个类中。嵌套类作为包含它的类的一个成员变量。如果定期被调用的的一个特定方法需要一个类,就可以构造一个嵌套类,此嵌套类的唯一任务就是定期调用所需的方法。这消除了对程序的其他部分的相依性,并使代码进一步模块化。清单 9,一个图形时钟的基础,使用了内部类。
public class Clock {
protected class Refresher extends Thread {
int refreshT
public Refresher(int x) {
super(&Refresher&);
refreshTime =
public void run() {
while(true) {
sleep(refreshTime);
catch(Exception e) {}
repaint();
public Clock() {
Refresher r = new Refresher(1000);
r.start();
private void repaint() {
// 获取时间的系统调用
// 重绘时钟指针
清单 9 中的代码示例不靠任何其他代码来调用 repaint() 方法。这样,将一个时钟并入一个较大的用户界面就相当简单。
当应用程序需要对事件或条件(内部的和外部的)作出反映时,有两种方法或用来设计系统。在第一种方法(称为轮询)中,系统定期确定这一状态并据此作出反映。这种方法(虽然简单)也效率不高,因为您始终无法预知何时需要调用它。
第二种方法(称为事件驱动处理)效率较高,但实现起来也较为复杂。在事件驱动处理的情况下,需要一种发信机制来控制某一特定线程何时应该运行。在 Java 程序中,您可以使用 wait()、notify() 和 notifyAll() 方法向线程发送信号。这些方法允许线程在一个对象上阻塞,直到所需的条件得到满足为止,然后再次开始运行。这种设计减少了 CPU 占用,因为线程在阻塞时不消耗执行时间,并且可在 notify() 方法被调用时立即唤醒。与轮询相比,事件驱动方法可以提供更短的响应时间。
编写线程安全类的最简单的方法是用 synchronized 声明每个方法。虽然这种方案可以消除数据损坏,但它同时也会消除您预期从多线程获得的任何收益。这样,您就需要分析并确保在 synchronized 块内部仅占用最少的执行时间。您必须格外关注访问缓慢资源 ― 文件、目录、网络套接字和数据库 ― 的方法,这些方法可能降低您的程序的效率。尽量将对这类资源的访问放在一个单独的线程中,最好在任何 synchronized 代码之外。
&被设计为要处理的文件的中心储存库。它与使用 getWork() 和 finishWork()
与 WorkTable 类对接的一组线程一起工作。本例旨在让您体验一下全功能的线程安全类,该类使用了 helper 线程和混合同步。请注意继续添加要处理的新文件的Refresher helper 线程的用法。本例没有调整到最佳性能,很明显有许多地方可以改写以改善性能,比如将 Refresher 线程改为使用 wait()/notify() 方法事件驱动的,改写 populateTable() 方法以减少列出磁盘上的文件(这是高成本的操作)所产生的影响。
通过使用可用的全部语言支持,Java 程序中的多线程编程相当简单。但是,使线程安全类具有较高的效率仍然比较困难。为了改善性能,您必须事先考虑并谨慎使用锁定功能。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:430263次
积分:10204
积分:10204
排名:第1431名
原创:432篇
译文:218篇
评论:60条
(12)(30)(6)(1)(1)(1)(5)(5)(2)(3)(32)(27)(36)(2)(2)(18)(23)(18)(18)(30)(60)(101)(161)(59)(4)普通对象的线程安全,有一个已经初始化的实例,为了线程安全想每个线程都copy一下对象,而不改变原来对象的值
楼主能否补充一下详细内容
--- 共有 3 条评论 ---
: 是的,但是他有成员变量
: 最简单的办法就是这个对象不要持有任何成员,这个对象就是线程安全的。
就是给你一个对象,通过这个对象来执行很多操作,但是要保证线程安全
不知道你说的是共享同一对象,还是在不同线程clone多个对象。如果是后者的话实现Clonable接口就可以了;
但是如果要线程共享对象,最粗暴的方式就是用synchronize或lock来同步,除非你这个需要共享的对象是immutable的,那就随意共享。
--- 共有 5 条评论 ---
: 怎么搞呢
: 你这个情况有点复杂,如果对象的状态变化会导致成员对象状态也变化的话,clone方法有点难写。还是考虑用同步吧
: 对clone不是很了解,深度引用的属性对象有没有问题,性能有没有问题
: 自己写一个这个类的子类,同时继承clonable呢?
对象没有实现 clonable,原对象immutable的,各个线程里都有操作该对象,必须可以操作只对当前线程有效,操作完了,原对象也不能改变,因为只有一个对象 放在threadlocal里,线程操作也是会改变原对象的
ThreadLocal
--- 共有 3 条评论 ---
每个线程可以用 也可以改 只针对自己的当前线程有效
但原对象不能变
: 这个对象的数据是共享的?
那是线程共享
但是会改变原对象
现在是不想改变元对象 相当于copy一份
没有实现clonable
不太明白,你的意思是,你有一个共享不可变对象,而每个线程可以对其读写操作,但结束后共享对象就会变回原样?
--- 共有 3 条评论 ---
: 每个线程去读取的时候 久得是初始状态的对象
: 那如你所说的,有什么问题吗?
我又反复思考推测(因为有些矛盾点),你的这个共享对象,有些方法,依赖内部某些属性,然后这些属性不能受到其他线程的干扰而且又能恢复原数据,所以你就想通过克隆对象保持数据一致性实现临时操作。
我这么说吧,克隆对象远没有你想的那么简单,除了对象比较,还有深复制浅复制,必要时的复制策略,甚至是复制对象后的开销,复制对象的结构越复杂、线程越多,开销就越大。假如你只是修改一个变量,而克隆了一个需要复制上百个对象的共享对象,你觉得压力大么。当然,你的共享只由基本数据类型组合而来的话,随便你怎么克隆。
所以我还是建议你修改下共享对象的实现,把它改成贫血模式,然后再写一个相关的操作实现类,由操作实现类保存各线程的临时数据。
--- 共有 1 条评论 ---
好复杂的赶脚设计一个线程安全的类(转)
我的图书馆
设计一个线程安全的类(转)
转自:/blog//thread-safe-class-design/
Apple的框架
首先让我们来看一下Apple的框架。一般情况下,除非提前声明,否则大多数类默认不是线程安全的。一些是我们所期望的,但是另一些却会相当有趣。
其中甚至有经验的iOS/Mac开发人员常会犯的错误是在后台线程中访问部分UIKit/AppKit。最容易犯的错误是在后台线程中对property赋值,比如图片,因为他们的内容是在后台从网络上获取的。Apple的代码是性能优化过的,如果你从不同线程去改动property,它是不会警告你的。
例如图片这种情况,一个常见的问题是你的改动会产生延迟。但是如果两个线程同时设置图片,很可能你的程序将直接崩溃,因为当前设置的图片可能会被释放两次。由于这是和时机相关的,因此崩溃通常发生在客户使用时,而并不是在开发过程中。
虽然没有官方的工具来发现这样的错误,但是有一些技巧可以避免这种错误发生。是一小段代码,可以修补任何调用UIView的setNeedsLayout和setNeedsDisplay,以及在发送调用之前检查是否执行在主线程。由于这两种方法被许多UIKit的setters方法调用(包括图片),这将会捕获许多线程相关的错误。虽然这个不使用私有API,但是我们不建议在产品程序中使用,而是最好在开发过程是使用。
UIKit非线程安全是Apple有意的设计决定。从性能方面来说线程安全没有太多好处,它实际上会使很多事情变慢。而事实上UIKit和主线程捆绑使它很容易编写并发程序和使用UIKit。你所需要做的就是确保总是在主线程上调用UIKit。
为什么UIKit不是线程安全的?
像UIKit这样大的框架上确保线程安全是一个重大的任务,会带来巨大的成本。改变非原子property为原子property只是所需要改变的一小部分。通常你想要一次改变多个property,然后才能看到更改的结果。对于这一点,Apple不得不暴露一个方法,像CoreData的performBlock:和同步的方法performBlockAndWait:。如果你考虑大多数调用UIKit类是有关配置(configuration),使他们线程安全更没有意义。
然而,即使调用不是关于配置(configuration)来共享内部状态,因此它们不是线程安全的。如果你已经写回到黑暗时代iOS3.2及以前的应用程序,你一定经历过当准备背景图像时使用NSString的drawInRect:withFont:随时崩溃。值得庆幸的是随着iOS4的到来,Apple提供了大部分绘图的方法和类,例如UIColor和UIFont在后台线程中的使用。
不幸的是,Apple的文档目前还缺乏有关线程安全的主题。他们建议只在主线程访问,甚至连绘画方法他们都不能保证线程安全。所以阅读iOS的版本说明总是一个好主意。
在大多数情况下,UIKit类只应该在程序的主线程使用。无论是从UIResponder派生的类,还是那些涉及以任何方式操作你的应用程序的用户界面。
解除分配问题
另一个在后台使用UIKit对象的风险是“解除分配问题”。Apple在TN2109里概括了这个问题,并提出了多种解决方案。这个问题是UI对象应该在主线程中释放,因为一部分对象有可能在dealloc中对视图层次结构进行更改。正如我们所知,这种对UIKit的调用需要发生在主线程上。
由于它常见于次线程,操作或块保留调用者,这很容易出错,并且很难找到并修复。这也是在AFNetworking中长期存在的一个bug,只是因为不是很多人知道这个问题,照例,显然它很罕见,并且很难重现崩溃。在异步块操作里一贯使用__weak和不访问ivars会有所帮助。
Apple有一个很好的概述文档,对iOS和Mac上列出线程安全最常见的基础类。一般情况下,不可变类,像NSArray是线程安全的,而它们的可变的变体,像NSMutableArray则不是。事实上,当在一个队列中序列化的访问时,是可以在不同线程中使用它们的。请记住,方法可能返回一个集合对象的可变变体,即使它们生命它们的返回类型是不可变的。好的做法是写一些像return [array copy]来确保返回的对象实际上是不可变的。
不同于像Java语言,Foundation框架不提供框架外的线程安全的集合类。其实这是非常合理的,因为在大多数情况下,你想在更高层使用你的锁去避免过多的锁操作。一个值得注意的例外是缓存,其中一个可变的字典可能会保存不变的数据-在这里Apple在iOS4中增加了NSCache,它不仅能锁定访问,还可以在低内存情况下清除它的内容。
这就是说,在你的程序中,这也许是有效的情况,其中一个线程安全的可变的字典可以很轻便的。而这要归功于类簇(class cluster)的解决方案,它可以很容易的写一个。
原子属性(properties)
有没有想过Apple如何处理原子设置/获取属性?现在你可能已经听说过spinlocks, semaphores, locks, @synchronized – 那Apple使用什么?幸运的是,Objective-C运行是公开的,所以我们可以看看幕后发生了什么。
一个非原子属性的setter方法可能看起来像这样:
- (void)setUserName:(NSString *)userName {
if (userName != _userName) {
[userName retain];
[_userName release];
_userName = userN
这是手动retain/release变量,然而用ARC生成的代码看起来类似。让我们看看这段代码,很显然当setUserName:被同时调用就遇到了麻烦。我们最终可能会释放_userName两次,这会破坏内存,并且导致难以发现的bug。
对于任意一个非手工实现的property内部发生的是,编译器生成一个调用objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)。在我们的例子中,调用参数是这样的:
objc_setProperty_non_gc(self, _cmd,
(ptrdiff_t)(&_userName) - (ptrdiff_t)(self), userName, NO, NO);
ptrdiff_t你可能看起来很怪异,但最终它是一个简单的指针算法,因为一个Objective-C类正是另一个C结构。
objc_setProperty调用下面的方法:
static inline void reallySetProperty(id self, SEL _cmd, id newValue,
ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:NULL];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:NULL];
if (*slot == newValue)
newValue = objc_retain(newValue);
if (!atomic) {
oldValue = *
*slot = newV
spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
_spin_lock(slotlock);
oldValue = *
*slot = newV
_spin_unlock(slotlock);
objc_release(oldValue);
除了相当有趣的名字,这种方法其实是相当简单,并使用128个在PropertyLocks可用的spinlocks其中之一。这是一个务实的和快速的解决方案 – 最坏的情况是,因为一个哈希冲突,一个setter不得不等待一个不相关的setter结束。
虽然这些方法在任何公共头文件都没有声明,但可以手动调用它们。我并不是说这是一个好主意,但如果你想要原子属性和想要同时实现setter,知道这些是很有趣的并且可能会相当有用。
// Manually declare runtime methods.
extern void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset,
id newValue, BOOL atomic, BOOL shouldCopy);
extern id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset,
BOOL atomic);
#define PSTAtomicRetainedSet(dest, src) objc_setProperty(self, _cmd,
(ptrdiff_t)(&dest) - (ptrdiff_t)(self), src, YES, NO)
#define PSTAtomicAutoreleasedGet(src) objc_getProperty(self, _cmd,
(ptrdiff_t)(&src) - (ptrdiff_t)(self), YES)
全部片段包括处理结构的代码。但是请记住我们不建议使用这个。
@synchronized如何?
你可能很好奇为什么Apple不使用一个已有的运行时特性@synchronized(self)来做属性锁。一旦你看了源代码,你将明白这还有很多事要做。Apple采用最多三个上锁/解锁序列,部分原因是他们还增加了异常展开(exception unwinding)。比起更加快速的spinlock方案,这个会慢一些。由于设置属性通常是相当快的,spinlocks是最完美的选择。当你需要确保没有代码死锁而抛出异常,@synchronized(self)是个好的选择。
你自己的类
单独使用原子属性不会让你的类线程安全的。它只会保护你在setter中免受竞态条件(race conditions),但不会保护你的应用程序逻辑。请考虑以下代码片段:
if (self.contents) {
CFAttributedStringRef stringRef = CFAttributedStringCreate(NULL,
(__bridge CFStringRef)self.contents, NULL);
// draw string
我在PSPDFKit早早就犯了这个错误。偶尔,当contents属性检查后被设置为nil,该应用程序以EXC_BAD_ACCESS崩溃了。对这个问题简单的解决办法是捕获变量:
NSString *contents = self.
if (contents) {
CFAttributedStringRef stringRef = CFAttributedStringCreate(NULL,
(__bridge CFStringRef)contents, NULL);
// draw string
这样就解决了问题,但在大多数情况下,它不是那么简单的。试想一下,我们也有一个textColor属性,我们在一个线程中改变两次属性。那么,我们的渲染线程可能最终会使用有旧颜色值的新内容,我们得到一个奇怪的组合。这就是为什么Core Data在一个线程或队列中绑定模型对象。
对于这个问题没有一个统一标准的解决方案。使用不可变的模型是一个解决方案,但它有它自己的问题。另一种方法是限制在主线程或一个特定的队列更改现有对象,而在工作线程中使用之前生成的副本。我推荐Jonathan Sterling在文章中为解决这个问题更多的想法。
简单的解决方法是使用@synchronize。其他的是非常,非常有可能让你陷入困境。更聪明的人一次又一次地在其他方法上失败了。
实用的线程安全设计
在试图做线程安全之前,认真考虑是否是必要的。请确保它不是过早的优化。如果它像是一个配置类,考虑线程安全是没有意义的。更好的方法是抛出一些断言来确保它的正确使用:
void PSPDFAssertIfNotMainThread(void) {
NSAssert(NSThread.isMainThread,
@"Error: Method needs to be called on the main thread. %@",
[NSThread callStackSymbols]);
现在肯定有线程安全的代码,一个很好的例子就是缓存类。一个好的方法是使用一个并行dispatch_queue为读/写锁,以最大限度地提高性能,并尝试只锁定那些真正需要的地方。一旦你开始使用多个队列用于锁定不同部位,事情将很快变得棘手。
有时候,你也可以重写你的代码,使特殊的锁不是必需的。考虑这个代码片段,是一个多播委托的形式。 (在许多情况下,使用NSNotifications会更好,但也有有效的多路广播委托用例。)
@property (nonatomic, strong) NSMutableSet *
// in init
_delegateQueue = dispatch_queue_create("com.PSPDFKit.cacheDelegateQueue",
DISPATCH_QUEUE_CONCURRENT);
- (void)addDelegate:(id&PSPDFCacheDelegate&)delegate {
dispatch_barrier_async(_delegateQueue, ^{
[self.delegates addObject:delegate];
- (void)removeAllDelegates {
dispatch_barrier_async(_delegateQueue, ^{
self.delegates removeAllObjects];
- (void)callDelegateForX {
dispatch_sync(_delegateQueue, ^{
[self.delegates enumerateObjectsUsingBlock:^(id&PSPDFCacheDelegate& delegate, NSUInteger idx, BOOL *stop) {
// Call delegate
除非addDelegate:或removeDelegate:每秒被调用上千次,否则下面是更简洁的方法:
@property (atomic, copy) NSSet *
- (void)addDelegate:(id&PSPDFCacheDelegate&)delegate {
@synchronized(self) {
self.delegates = [self.delegates setByAddingObject:delegate];
- (void)removeAllDelegates {
self.delegates =
- (void)callDelegateForX {
[self.delegates enumerateObjectsUsingBlock:^(id&PSPDFCacheDelegate& delegate, NSUInteger idx, BOOL *stop) {
// Call delegate
当然,这个例子有点儿认为构造的,它可以简单的局限于在主线程更改。但对于许多数据结构,在修改方法中创建不可变的副本是值得的,让广大的应用程序逻辑并不需要处理过多的锁定。注意,我们仍然要在addDelegate:申请锁,否则如果委托对象被来自不同的线程同时调用,它可能会迷失。
对于大部分的锁定需求,GCD是完美的。这很简单,很快速,并且它的基于块的API使得它更难偶然做出不平衡锁。不过,也有不少缺陷,我们将要在这里探索其中一些。
使用GCD作为递归锁
GCD是一个队列来序列化访问共享资源。这可以被用于锁定,但它比@synchronized大不相同。 GCD队列是不可重入的 – 这将打破队列特性。许多人试图使用dispatch_get_current_queue()来作为替代方案,这是一个坏主意。Apple在iOS6中废弃此方法自然有它的原因。
// This is a bad idea.
inline void pst_dispatch_sync_reentrant(dispatch_queue_t queue,
dispatch_block_t block)
dispatch_get_current_queue() == queue ? block()
: dispatch_sync(queue, block);
测试当前队列简单的解决方案可能起作用,但当你的代码变得更加复杂的时候,你可能会在同一时间对多个队列上锁,它会失败。一旦你是这种情况,你几乎肯定会遇到死锁。当然,人们可以使用dispatch_get_specific(),它会遍历整个队列的层次结构来测试特定的队列。对于您将不得不编写应用此元数据的自定义队列的构造函数。不要走那条路,很多使用情况下,NSRecursiveLock是更好的解决方案。
dispatch_async的固定时序问题
在UIKit中有一些时序问题?大多数时候,这将是完美的“修复”:
dispatch_async(dispatch_get_main_queue(), ^{
// Some UIKit call that had timing issues but works fine
// in the next runloop.
[self updatePopoverSize];
相信我,不要这样做。这将在以后缠着你因为你的应用程序变得越来越大。这是超级难调试,并因为“时序问题”当你需要调度越来越多,事情很快会土崩瓦解。看你的代码,找到适当调用的位置(例如viewWillAppear而不是viewDidLoad中)。在我的代码库仍然有一些黑客方式,但大部分都会被适当的记录并且提交问题。
请记住,这真不是GCD特有的,但它是一个常见的反模式,只是GCD很容易做到。你可以使用同样的才智performSelector:afterDelay:,其中下一个runloop的延迟是0.f。
在性能关键代码中使用混合dispatch_sync和dispatch_async
那个花了我一段时间才弄清楚。在PSPDFKit中有一个使用LRU列表来跟踪图像访问的缓存类。当你通过页面滚动,它会被调用很多次。最初的实现中对于可用的访问使用dispatch_sync,用dispatch_async来更新LRU位置。这导致帧速率远远低于每秒60帧的目标。
当你的应用程序中运行的其他代码阻止GCD的线程,它可能需要一段时间,直到调度管理器发现一个线程来执行dispatch_async代码 – 在那之前,你的同步调用将被阻塞。即使,在这个例子中,在异步情况下执行的顺序并不重要,没有简单的方法来告诉给GCD 。读/写锁在这里不会有任何帮助,因为异步流程非常肯定需要执行一个写屏障,在这期间你的所有读操作都会被锁定。教训:如果滥用, dispatch_async可以是昂贵的。使用它来锁操作要非常小心。
使用dispatch_async来调度内存密集型操作
我们已经谈了很多关于NSOperations ,而且使用更高层的API通常是一个好主意。如果你处理的是内存密集型操作的工作块,这是尤其如此。
在旧版本的PSPDFKit中,我用了一个GCD队列来调度写缓存JPG图像到磁盘。当视网膜的iPad出来了,这开始引起麻烦。分辨率加倍,比起渲染图像,对图像数据进行编码需要更长的时间。因此,操作堆积在队列中,当系统繁忙它可能会因为内存耗尽而崩溃。
没有办法来看到有多少操作在排队里(除非你手动添加代码来追踪这一点) ,而且也没有内置的方式来取消操作万一收到内存不足的通知。切换到NSOperations使代码更加可调试,并允许这一切都无需编写手动管理代码。
当然也有一些注意事项,例如你不能在你的NSOperationQueue上设置一个目标队列(如为节流的I/O而DISPATCH_QUEUE_PRIORITY_BACKGROUND&) 。但是,这是一个为可调试性付出的很小的代价,也防止你陷入类似问题,如优先级反转。我甚至建议使用漂亮的NSBlockOperation API,并建议NSOperation的真正子类,包括描述的实现。这是更多的工作,但后来,有一个方法出奇的有用是打印所有运行/挂起的操作。
TA的最新馆藏[转]&[转]&[转]&[转]&[转]&

我要回帖

更多关于 放开我北鼻第一季资源 的文章

 

随机推荐