美文网首页
iOS高性能编程

iOS高性能编程

作者: 獨荹儛臨 | 来源:发表于2018-04-10 14:00 被阅读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

    相关文章

      网友评论

          本文标题:iOS高性能编程

          本文链接:https://www.haomeiwen.com/subject/coaghftx.html