iOS高性能编程

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281