iOS高性能编程

2018-04-10  本文已影响63人  獨荹儛臨

iOS虽然应用有多个线程看起来非常赞,但每个线程都有一定的开销,从而影响到应用的性能。线程不仅仅有创建时的时间开销,还会消耗内核的内存,即应用的内存空间。

内核数据结构

每个线程大约消耗 1KB 的内核内存空间。这块内存用于存储与线程有关的数据结构和属 性。这块内存是联动内存(wired memory),无法被分页。

栈空间

主线程的栈空间大小为 1M,而且无法修改。所有的二级线程默认分配 512KB 的栈空间。 注意,完整的栈并不会立即被创建出来。实际的栈空间大小会随着使用而增长。因此,即使主线程有 1MB 的栈空间,某个时间点的实际栈空间很可能要小很多。
在线程启动前,栈空间的大小可以被改变。栈空间的最小值是 16KB,而且其数值必须是 4KB 的倍数。

+(NSThread *)createThreadWithTarget:(id)target selector:(SEL)selector
object:(id)argument stackSize:(NSUInteger)size {
if( (size % 4096) != 0) { return nil; }
         NSThread *t = [[NSThread alloc] initWithTarget:target
             selector:selector object:argument];
         t.stackSize = size;
return t; }

创建耗时

在 iPhone 6 Plus iOS 8.4 上进行了一项快速测试,展示了线程创建的耗时(不包含启动时间),其区间范围在 4000~5000 微秒,即 4~5 毫秒。创建线程后启动线程的耗时区间为 5~100 毫秒,平均大约在 29 毫秒。这是很大的时间开销,若在应用启动时开启多个线程,则尤为明显。线程的启动时间之所以如此之长,是因为多次的上下文切换所带来的开销。出于简洁的目的,我们省略了计算的代码。要想了解细节,你可以参考GitHub(https://github.com/gvaish/hpios/blob/master/src/ViewControllers/HPChapter05ViewController.m)中的 computeThreadCreationTime 方法。

GCD

GCDAPI(https://developer.apple.com/library/mac/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html)由核心语言特性、运行时库以及对执行并行代码的系统增强所组成
GCD 提供的功能列表:

• 任务或分发队列,允许主线程中的执行、并行执行和串行执行。
• 分发组,实现对一组任务执行情况的跟踪,而与这些任务所基于的队列无关。
• 信号量。
• 屏障,允许在并行分发队列中创建同步的点。
• 分发对象和管理源,实现更为底层的管理和监控。
• 异步 I/O,使用文件描述符或管道。

GCD 同样解决了线程的创建与管理。它帮助我们跟踪应用中线程的总数,且不会造成任何 的泄漏。

大多数情况下,应用单独使用 GCD 就可以很好地工作,但仍有特定的情况 需要考虑使用 NSThread 或 NSOperationQueue。当应用中有多个长耗时的任 务需要并行执行时,最好 . 对线程的创建过程加以控制。如果代码执行的时 间过长,很有可能达到线程的限制 64 个,2,3 即 GCD 的线程池上限。 应该避免浪费地使用 dispatch_async 和 dispatch_sync,因为那会导致应用 崩溃 4。虽然 64 个线程对移动应用来说是个很高的合理值,但不加控制的应 用迟早会超出这个限制。

操作与队列

操作和操作队列是 iOS 编程中和任务管理有关的又一个重要概念。
NSOperation 封装了一个任务以及和任务相关的数据和代码,而 NSOperationQueue 以先入先出的顺序控制了一个或多个这类任务的执行。
NSOperation 和 NSOperationQueue 都提供控制线程个数的能力。可用 maxConcurrentOpera- tionCount 属性控制队列的个数,也可以控制每个队列的线程个数。
在使用 NSThread(开发人员管理全部并发)和 GCD(OS 管理并发)之间存在两个选择。以下是对 NSThread、NSOperationQueue 和 GCD API 的一个快速比较。

GCD

♦ 抽象程度最高。
♦ 两种队列开箱即用:main 和 global。
♦ 可以创建更多的队列(使用 dispatch_queue_create)。
♦ 可以请求独占访问(使用 dispatch_barrier_sync 和 dispatch_barrier_async)。
♦ 基于线程管理。
♦ 硬性限制创建 64 个线程。

NSOperationQueue

♦ 无默认队列。
♦ 应用管理自己创建的队列。
♦ 队列是优先级队列。
♦ 操作可以有不同的优先级(使用 queuePriority 属性)。
♦ 使用 cancel 消息可以取消操作。注意,cancel 仅仅是个标记。如果操作已经开始 执行,则可能会继续执行下去。
♦ 可以等待某个操作执行完毕(使用 waitUntilFinished 消息)。

NSThread

♦ 低级别构造,最大化控制。
♦ 应用创建并管理线程。
♦ 应用创建并管理线程池。
♦ 应用启动线程。
♦ 线程可以拥有优先级,操作系统会根据优先级调度它们的执行。
♦ 无直接 API 用于等待线程完成。需要使用互斥量(如 NSLock)和自定义代码。

线程安全的代码

贯穿软件开发的职业生涯,我们总是被教导要编写线程安全的代码,这也就是说,如果有多个线程并行地执行同一组指令,不能产生任何副作用。以下两大类技术可以实现这一点。
• 不要使用可修改的共享状态。
• 如果无法避免使用可修改的共享状态,则确保你的代码是线程安全的。
这些技术说起来容易做起来难。要实现它们有多种选择。 因为应用会包含可修改的共享状态,所以我们需要掌握管理和修改共享状态的最佳实践。 驱动这些最佳实践的一条基本规则是“在代码中保留不变量”。
使用 @synchronized 指令可以创建一个信号量,并进入临界区,临界区在任何时刻都只能 被一个线程执行

@implementation HPUpdaterService
-(void)updateUser:(HPUser *)user properties:(NSDictionary *)properties { 
@synchronized(user) { 
            NSString *fn = [properties objectForKey:@"firstName"]; if(fn != nil) {
                 user.firstName = fn;
             }
            NSString *ln = [properties objectForKey:@"lastName"]; if(ln != nil) {
                 user.lastName = ln;
             }
} }
@end

锁是进入临界区的基础构件。atomic 属性和 @synchronized 块是为了实现便捷实用的高级别抽象。
以下是三种可用的锁。

• NSLock

这是一种低级别的锁。一旦获取了锁,执行则进入临界区,且不会允许超过一个线程并 行执行。释放锁则标记着临界区的结束。

@interface ThreadSafeClass () { 
        NSLock *lock; 
}
@end
-(instancetype)init { 
    if(self = [super init]) {
        self->lock = [NSLock new]; }
         return self;
 }
-(void)safeMethod {
       [self->lock lock]; 
      //线程安全的代码 
       [self->lock unlock]; 
}

• NSRecursiveLock

在调用 lock 之前,NSLock 必须先调用 unlock。但正如名字所暗示的那样, NSRecursiveLock 允许在被解锁前锁定多次。如果解锁的次数与锁定的次数相匹配,则 认为锁被释放,其他线程可以获取锁。
当类中有多个方法使用同一个锁进行同步,且其中一个方法调用另一个方法时, NSRecursiveLock 非常有用。

@interface ThreadSafeClass () { 
NSRecursiveLock *lock; 
} 
@end
-(instancetype)init { 
    if(self = [super init]) {
          self->lock = [NSRecursiveLock new];
         }
      return self; 
}
-(void)safeMethod1 { 
[self->lock lock]; 
[self safeMethod2]; 
 [self->lock unlock]; 
}
-(void)safeMethod2 {
[self->lock lock]; 
//线程安全的代码
[self->lock unlock];  }

• NSCondition

有些情况需要协调线程之间的执行。例如,一个线程可能需要等待其他线程返回结果。 NSCondition 可以原子性地释放锁,从而使得其他等待的线程可以获取锁,而初始的线 程继续等待。 一个线程会等待释放锁的条件变量。另一个线程会通知条件变量释放该锁,并唤醒等待中的线程。

有兴趣学习的可以加群一起来探讨: 里面也有一些开发相关的PDF文档

群号:727323882

上一篇下一篇

猜你喜欢

热点阅读