本系列所有开发文档翻译链接地址:iOS7开发-Apple苹果iPhone开发Xcode官方文档翻译PDF下载地址(2013年12月29日更新版)
多线程
技术博客http://www.cnblogs.com/ChenYilong/ 新浪微博http://weibo.com/luohanchenyilong
多线程的应用
• 耗时操作,例如网络图片、视频、歌曲、书籍等资源下载 • 游戏中的声音播放
多线程示意图
• 充分发挥多核处理器的优势,并发(同时执行) 执行任务让系统运行的更快、更流畅
进程与线程概念
• 一个运行的程序就是一个进程或者叫做一个任务
• 一个进程至少包含一个线程,线程是程序的执行流
• iOS程序启动时,在创建一个进程的同时, 会开始运行一个线程,该 线程被称为主线程
• 主线程是其他线程最终的父线程,所有界面的显示操作必须在主线程 进行
• 后台线程无法更新UI界面和响应用户点击事件
• 系统中的每一个进程都有自己独立的虚拟内存空间,而同一个进程中
的多个线程则共用进程的内存空间
• 每创建一个新的线程,都会消耗一定内存和CPU时间
• 当多个线程对同一个资源出现争夺的时候需要注意线程安全问题
多线程的优势与难点
• 优势
– 充分发挥多核处理器优势,将不同线程任务分配给不同的处
理器,真正进入“并行运算”状态
– 将耗时、轮询或者并发需求高等任务分配到其他线程执行, 并由主线程负责统一更新界面会使得应用程序更加流畅,用 户体验更好
– 当硬件处理器的数量增加,程序会运行更快,而无需做任何 调整
• 难点
– 共享资源的“争夺”
– 多线程是为了同步完成多项任务,不是为了提高运行效率, 而是为了通过提高资源使用效率来提高系统的整体性能
多线程使用注意事项
• 线程使用不是无节制的– iOS中的主线程的堆栈大小是1M
– 从第二个线程开始都是512KB
– 这些数值不能通过编译器开关或线程API函数更改
• 只有主线程有直接修改UI的能力
iOS的三种多线程技术
程)
2. 以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去 关心线程的具体使用问题
.1. NSOperation/NSOperationQueue 面向对象的线程技术
.2. GCD —— Grand Central Dispatch (派发) 是基于 C 语言的框架, 可以充分利用多
核 ,是苹果推荐使用的多线程技术
.
以上这三种编程方式从上到下,抽象度层次是从低到高的, 抽象度越高 的使用越简单,也是 Apple 最推荐使用的 。但是就目前而言, iOS 的开发者, 需要了解三种多线程技术的基本使用过程。因为很多框架技术分别使用 了不同多线程技术。

三种多线程技术的对比
• NSThread:
– 优点: NSThread 比其他两个轻量级, 使用简单
– 缺点: 需要自己管理线程的生命周期、线程同步、加锁、睡眠以 及唤醒等 。线程同步 对数据的加锁 会有一定的系统开销
• NSOperation :
– 不需要关心线程管理,数据同步的事情,可以把精力放在自己需
要执行的操作上
– NSOperation 是面向对象的 • GCD :
– Grand Central Dispatch 是 由苹果开发的一个多核编程的解决方案 。 iOS4.0+ 才能使用,是替代 NSThread , NSOperation 的高效和强大 的技术
– GCD 是基于 C 语言的

演练( 1 ) NSObject 的多线程方法 —— 准备
.1. 创建一个耗时较长的操作
.2. 创建一个耗时较短的操作
.3. 在界面中心放置两个按钮,分别调用 这两个任务
.4. 使用 [NSThread currentThread] 分别打 印各个任务所在的线程
.5. 运行观察效果
• 提示:
• NSLog 是一个相当耗时的操作 在应用 程序正式发布前,一定记住需要对应用 中的 NSLog 方法进行处理
• 无论使用哪一种多线程技术,均可以使 用 [NSThread currentThread] 查看当前任 务所在线程




NSObject 的多线程方法 —— 后台线程
- ( void )performSelectorInBackground:( SEL )aSelector
withObject:( id )arg
• 通常,由于线程管理相对比较繁琐,而很多耗时的任务又无法知道其准 确的完成时间,因此可以使用 performSelectorInBackground 方法 直接新建一个后台线程,并将选择器指定的任务在后台线程执行,而无 需关心具体的 NSThread 对象
• 提示:
– performSelectorInBackground 方法本身是在主线程中执行的,
而选择器指定的方法是在后台线程中进行的
– 使用 performSelectorInBackground 方法调用的任务可以更新
UI 界面 – 在大型交互式游戏中,通常使用此方法在后台线程播放音效

@autoreleasepool
• Objective-C 可以凭借 @autoreleasepool 使用内存资源,并需要时回 收资源
• 每个线程都需要有 @autoreleasepool ,否则可能会出现内存泄漏

NSObject 的多线程方法 —— 主线程
- ( void )performSelectorOnMainThread:( SEL )aSelector
withObject:( id )arg waitUntilDone:( BOOL )wait;
• 如果要更新 UI 界面,可以在后台线程中调用
performSelectorOnMainThread 方法
• 提示 :尽管使用 performSelectorInBackground 方法调用的任务 可以更新 UI 界面,但是在实际开发中,涉及到 UI 界面的更新操作,还 是要使用 performSelectorOnMainThread 方法,以避免不必要的 麻烦

NSObject 的多线程小结
- ( void )performSelectorInBackground:( SEL )aSelector withObject:( id )arg
• 在后台线程中通知主线程执行任务的方法 – ( void )performSelectorOnMainThread:( SEL )aSelector
withObject:( id )arg waitUntilDone:( BOOL )wait; • 获取线程信息
[ NSThread currentThread ]; • 线程休眠
[ NSThread sleepForTimeInterval : 1.0f ];
• 特点: – 使用简单,量级轻 – 不能控制线程的执行顺序

NSThread • 创建线程方法:
1. + ( void )detachNewThreadSelector:( SEL ) selector toTarget:( id ) target withObject:( id ) argument ;
2. - ( id )initWithTarget:( id ) target selector: ( SEL ) selector object:( id ) argument ;
• 参数说明: – selector : 线程执行的方法 ,只能有一个参数,不能有返回值
– target : selector 消息发送的对象 – argument :传输给 target 的唯一参数,也可以是 nil

NSThread 演练 —— 加载图片
方法会直接启动线程方法
• initWithTarget 需要调用 start 方法才能够启动线程方 法


NSOperation & NSOperationQueue
• NSOperation 的两个子类 1. NSInvocationOperation 2. NSBlockOperation
• 工作原理:
• 注意事项:
.1. 使用多线程时通常需要控制线程的并发数 ,因为线程会消耗系统资源,
同时运行的线程过多,系统会变慢
.
.2. 使用以下方法可以控制并发的线程数量:
- ( void )setMaxConcurrentOperationCount:( NSInteger )cnt;

NSOperation 演练 —— 加载图片
.1. 不能直接使用 NSOperation
.2. 定义完操作后, 将添加到操作队列中 ,即可启动异步操作,否则操 作任务仍然在主线程中执行
.3. 使用 NSBlockOperation 更加简单直接
.4. 使用 setMaxConcurrentOperationCount 可以限制并发操作数
量,降低系统开销
.5. 使用 addDependency 可以建立操作之间的依赖关系,设定操作的执行 顺序





GCD • GCD 是基于 C 语言的框架
• 工作原理 :
– 让程序平行排队的特定任务,根据可用的处理资源, 安排它们在
任何可用的处理器上执行任务
– 要执行的任务可以是一个函数或者一个 block
– 底层是通过线程实现的,不过程序员可以不必关注实现的细节
– GCD 中的 FIFO 队列称为 dispatch queue ,可以保证先进来的任务先 得到执行
– dispatch_notify 可以实现 监听一组任务是否完成 ,完成后得 到通知
• GCD 队列 : 1. 全局队列: 所有添加到主队列中的任务都是并发执行的 2. 串行队列: 所有添加到串行队列中的任务都是顺序执行的 3. 主队列: 所有添加到主队列中的任务都是在主线程中执行的

获取队列的方法
• 全局队列(可能会开启多条线程) dispatch_queue_t queue =
dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 ); • 串行队列(只可能会开启一条线程)
dispatch_queue_t queue = dispatch_queue_create ( "myQueue" , DISPATCH_QUEUE_SERIAL );
• 主队列 dispatch_get_main_queue ();

GCD 任务的执行方式 —— 同步 & 异步
• 异步操作
– dispatch_async 在其他线程执行任务,会开启新的线程 – 异步方法无法确定任务的执行顺序
• 同步操作 – dispatch_sync 在当前在当前线程执行任务,不开启新的线程
– 同步操作与队列无关
– 同步方法会依次执行,能够决定任务的执行顺序 – 更新界面 UI 时,最好使用同步方法

GCD 演练 —— 加载图片
• GCD 的优点: – 充分利用多核 – 所有的多线程代码集中在一起,便于维护 – GCD 中无需使用 @autoreleasepool
– 如果要顺序执行,可以使用 dispatch_sync 同步方法 – dispatch_async 无法确定任务的执行顺序

单例模型
• 目的 : – 保证在内存中永远只有类的单个实例
• 建立方法 :
allocWithZone 方法是对象分配内存空间时,最终会调用的方法, 重写该方法,保证只会分配一个内存空间
3. 建立 sharedXXX 类方法,便于其他类访问

互斥锁的目的,一次只让一个线程访问资源,从而达到资源的线程安全。
在 iPhone 开发中,通常要尽量避免使用互斥锁!

多线程演练 —— 卖票
• 系统预设 – 共有 30 张票可以销售( 开发时可以少一些,专注实现) – 售票工作由两个线程并发进行 – 没有可出售票据时,线程工作停止 – 两个线程的执行时间不同,模拟售票人员效率不同 – 使用一个多行文本框公告售票进度(主线程更新 UI )
• 线程工作安排 – 主线程:负责更新 UI
– 线程 1 :模拟第 1 名卖票员 – 线程 2 :模拟第 2 名卖票员 – 两个线程 几乎 同时开始卖票


单线程卖票流程图


多线程卖票示意图

演练准备 —— 更新 UI 方法,由主线程调用
NSMutableString *str = [ NSMutableString stringWithString : [ self . textView text ]];
// 2. 追加文本 [str appendFormat : @"%@\n" , text];
// 3. 设置文本 [ self . textView setText :str];
// 4. 选中最末位置,实现自动滚动效果 NSRange range = NSMakeRange (str. length - 1 , 1 ); [ self . textView scrollRangeToVisible :range];

卖票演练
• 资源争夺
– 仅使用单例模式无法解决资源争夺问题
– 使用互斥锁 @synchronized 可以保证多个线程不会使用同一代码块,而且比 NSLock 具有更好的性能
– 为了保证属性安全,被争夺资源的属性应该设置为原子属性 atomic • GCD
– GCD 的多线程更加灵活、方便
– dispatch_group_notify 可以监听一组任务是否完成 。这个方法很有用,比如
你执行三个下载任务,当三个任务都下载完成后,才通知界面说已经完成
– 如果不需要监听一组任务,可以直接使用 dispatch_async 方法
• NSOperation
– 更新界面时使用 [[ NSOperationQueue
mainQueue ] addOperationWithBlock : 方法 • NSThread
– 涉及到线程调度问题,日常开发不建议使用


三种多线程技术的流程对比

关于 iOS 多线程使用的建议
以自行学习,目前不建议再继续深入
– 只能在主线程中更新 UI
– 共享数据争夺的处理 ,互斥锁 @synchronized ( self ) 和原子属
性 atomic
– 不要使用多种多线程技术去争夺同一个资源
• 使用多线程是为了处理并发操作的。如果有可能,我们不要去做抢资 源的事情 互斥锁的代价相当的昂贵

//转载请注明出处--本文永久链接:http://www.cnblogs.com/ChenYilong/p/3494799.html