线程是单个应用中能够并发执行多个代码路径的多种技术之一。虽然更新的技术如操做对象(Operation)和Grand Central Dispatch(GCD),提供一个等价现代化和高效的基础设施来实现多核并发,可是Mac OS 和IOS也提供一套接口来建立和管理线程。编程
第一章:关于多线程编程数据结构
处理器已经达到瓶颈限制,因此芯片开始转向多核,这就是为何要多核并发。多线程
1.1 什么是多线程并发
多线程是一个比较轻量级的方法来实现单个应用程序多个代码执行路径。app
在非并发程序中,只有一个执行程序,该线程开始和结束与你应用程序的main循环。一个个方法和函数的分支构成了你整个应用程序的全部行为。与此相反,支持并发的应用程序开始能够在须要额外的执行路径的时候建立一个或多个线程。每一个新的执行路径有它本身独立于应用程序main循环的定制开始循环。在应用程序中存在多个线程提供了两个很是重要的潜在优点。框架
多线程的问题:由于多个线程这间可能有交互,会访问相同的数据结构。若是两个程序试图同时访问相同的数据结构,那么容易对数据进行交叉影响。异步
1.2 编程术语socket
线程(thread):用于指代独立执行的代码段。分布式
进程(process):用于指代一个正在运行的可执行程序,他包含多个线程。函数
任务(task):用于指代抽象的概念,表示须要执行工做。
1.3 多线程替代方法
注意:当使用fork函数加载独立进程的时候,你必须老是在fork后面调用exec或者相似的函数。基于 Core Foundation、Cocao 或者 Core Data 框架(不管显式仍是隐式关联)的应用程序随后调用 exec 函数或者相似的函数都会导出不肯定的结果。
1.4 线程支持
1.4.1 线程包
多线程的底层实现机制是Mach的线程。可是不多使用Mach级的线程。咱们常用的时POSIX的API或者它的衍生工具。
应用程序中使用的线程技术:
线程状态:运行(running)、就绪(ready)、阻塞(blocked)。
建立线程必须指定该线程的入口点函数(Cocoa线程为入口点方法),函数是由你想在该线程上执行的代码组成,但函数返回的时候,或你显式的中断线程的时候,线程永久中止,而且被系统回收。由于它的内存和时间消耗很大,因此在入口函数要作至关数量的工做,或创建一个运行循环容许进行常常性工做。
1.4.2 Run Loops
Run Loop是用来在线程上管理事件异步到达的基础设施,一个run loop为线程检测一个或多个事件源。
1.4.3 同步工具
线程编程的危害就是多个资源争夺,因此咱们必须尽量的使用锁,条件,原子操做和其余技术来同步资源的访问。
锁:
提供了一次只有一个线程能够执行代码的有效保护形式。最简单的一种锁互斥排他锁(mutex)。
Cocoa提供了几个互斥排他锁的变种来支持不一样的行为类型,好比递归。
条件:
确保应用程序任务执行的适当顺序,一个条件做为一个看门人,阻塞给定的线程。POSIZ级别和基础框架都直接提供了条件的支持。(若是你使用操做对象,你能够配置你的操做对象之间的依赖关系的顺序确 定任务的执行顺序,这和条件提供的行为很是类似)。
原子操做:
能够执行标量数据类型的数学或逻辑运算,原子操做使用特殊的硬件设施来保证变量的改变在其余线程能够访问前完成。
1.4.4 线程间通讯
Mac OS X上面使用的通讯机制。(异常的消息队列和Cocoa分布式对象,这些也能够在IOS上通讯)
Mac OS X 和 iOS 经过其余 API 接口提供了隐式的并发支持。你能够考虑使用异步 API, GCD 方式,或操做对象来实现并发,而不是本身建立一个线程。这些技术背后为你作 了线程相关的工做,并保证是无误的。 此外,好比 GCD 和操做对象技术被设计用来管理线程,比经过本身的代码根据当前的负载调整活动线程的数量更高效。
1.5.2 保持你的线程合理的忙
1.5.3 避免共享数据结构
最简单的方法就是给你应用程序的每一个线程一份它需求的数据的副本。
1.5.4 多线程和你的用户界面
若是程序有图形界面,建议在主线程中接受和界面相关的时间和初始化更新你的界面。有利于避免与处理用户事件和窗口绘图相关的同步问题。
1.5.5 了解线程退出时的行为
若是你正在编程 Cocoa 的程序,你也能够经过使用 applicationShouldTerminate: 的委托方法来延迟程序的中断直到一段时间后或者完成取消。当延迟中断的时候,你 的程序须要等待直到任何周期线程已经完成它们的任务且调用了 replyToApplicationShouldTerminate:方法。
1.5.6 异常处理
若是你须要通知另外一个线程(好比主线程)当前线程中的一个特殊状况,你应该 捕捉异常,并简单地将消息发送到其余线程告知发生了什么事。根据你的模型和你正 在尝试作的事情,引起异常的线程能够继续执行(若是可能的话),等待指示,或者 干脆退出。
注意:在 Cocoa 里面,一个 NSException 对象是一个自包含对象,一旦它被引起了,那么它 能够从一个线程传递到另一个线程。
在一些状况下,异常处理多是自动建立的。好比,Objective-C 中的 @synchronized 包含了一个隐式的异常处理。
1.5.7 干净的中端你的线程
第二章:线程管理
2.1 线程成本
2.2 建立一个线程
2.2.1 使用NSThread
使用NSThread来建立线程有两个方法
使用 detachNewThreadSelector:toTarget:withObject:类方法来生成一个
新的线程。
建立一个新的 NSThread 对象,并调用它的 start 方法。(仅在 iOS 和 Mac OS X v10.5 及其以后才支持)
initWithTarget:selector:object:方法。该方法和 detachNewThreadSelector:toTarget:withObject:方法来初始化一个新的 NSThread 实例须要相同的额外开销。然而它并无启动一个线程。为了启动一个线程,你能够 显式调用先对象的 start 方法
2.2.2 使用POSIX的多线程
2.2.3 使用NSObject来生成一个线程
在 iOS 和 Mac OS X v10.5 及其以后,全部的对象均可能生成一个新的线程,并 用它来执行它任意的方法。方法 performSelectorInBackground:withObject:新生成一个脱离的线程,使用指定的方法做为新线程的主体入口点。
好比,若是你有一些对 象(使用变量 myObj 来表明),而且这些对象拥有一个你想在后台运行的 doSomething 的方法,你可使用以下的代码来生成一个新的线程:
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
调用该方法的效果和你在当前对象里面使用 NSThread 的 detachNewThreadSelector:toTarget:withObject:传递 selectore,object 做为参数的方法同样。
2.2.4 使用其余线程技术
尽管 POSIX 例程和 NSThread 类被推荐使用来建立低级线程,可是其余基于 C 语 言的技术在 Mac OS X 上面一样可用。在这其中,惟一一个能够考虑使用的是多处理 服务(Multiprocessing Services),它自己就是在 POSIX 线程上执行。多处理服务 是专门为早期的 Mac OS 版本开发的,后来在 Mac OS X 里面的 Carbon 应用程序上面 一样适用。若是你有代码真是有该技术,你能够继续使用它,尽管你应该把这些代码 转化为 POSIX。该技术在 iOS 上面不可用。
2.2.5 在cocoa程序中使用POSIX线程
2.3 配置线程属性
2.3.1 配置线程的堆栈大小
Technology |
Option |
||||
Cocoa |
In iOS and Mac OS X v10.5 and later, allocate and initialize an NSThread object (do not use thedetachNewThreadSelector:toTarget:withObject: method). Before calling the start method of the thread object, use thesetStackSize: method to specify the new stack size. |
||||
POSIX |
Create a new pthread_attr_t structure and use the pthread_attr_setstacksize function to change the default stack size. Pass the attributes to the pthread_create function when creating your thread. |
||||
Multiprocessing Services |
Pass the appropriate stack size value to the MPCreateTask function when you create your thread. |
2.3.2 配置线程本地存储
每一个线程都维护了一个键—值的字典,他能够在线程里面的任何地方被访问。你可使用该字典保存一些信息。
2.3.3 设置线程的脱离状态
大部分上层的线程技术都默认建立了脱离线程(Detached thread),他们容许系统在线程完成的时候当即释放他的数据结构。脱离线程不须要显示和你的应用程序交互。意味着线程检索的结果由你来决定。相比之下,系统不回收可链接线程(joinable thread)的资源,知道另外一个线程明确加入该线程,这个过程当中可能会阻止线程执行加入。
能够认为可链接线程相似于子线程,虽然能够做为独立线程运行,可是可链接线程在它资源能够呗系统回收以前必须被其余线程链接,可链接线程提供一个显示的方式把数据传递到其余线程。能够传递一个数据指针或者返回值给pthread_exit函数。其余函数能够经过pthread_join拿到这个数据。
重要:在应用程序退出时,脱离线程能够当即被中断,而可链接线程则不能够。每一个可链接 线程必须在进程被容许能够退出的时候被链接。因此当线程处于周期性工做而不容许被中断的时 候,好比保存数据到硬盘,可链接线程是最佳选择。
若是你想要建立可链接线程,惟一的办法是使用 POSIX 线程。POSIX 默认建立的 线程是可链接的。为了把线程标记为脱离的或可链接的,使用 pthread_attr_setdetachstate 函数来修改正在建立的线程的属性。在线程启动后, 你能够经过调用 pthread_detach 函数来把线程修改成可链接的。
2.3.4 设置线程的优先级
若是你想改变线程的优先级,Cocoa 和 POSIX 都提供了一种方法来实现。对于 Cocoa 线程而言,你可使用 NSThread 的 setThreadPriority:类方法来设置当前运 行线程的优先级。对于 POSIX 线程,你可使用 pthread_setschedparam 函数来实现。
2.4 编写你线程的主题入口点
2.4.1 建立一个自动释放池
在OC框架连接的应用程序,一般每个线程必须建立至少一个自动释放池。
若是你的应用使用内存管理模型,在你编写线程主体入口的时候第一件事情就是建立一个自动释放池,一样,在线程的最后应该销毁该自动释放池。
2.4.2 设置异常处理
在异常发生的地方捕捉而且处理它,可是若是在你的线程里面捕捉一个抛出的异常失败的话可能照成你的应用程序强退,在你线程的主要入口点安装一个try/catch模块,能够捕捉任何未知的异常,并提供一个合适的响应。
2.4.3 设置一个run loop
当你想编写一个独立运行的线程时,你有两个选择。第一种选择是写代码做为一个长期任务,不多甚至不中断,线程完成的时候退出。第二种是把线程放在一个循环中,让他动态的处理到来的任务请求。
cocoa,carbon,UIKit在你的应用程序的主线程中自动启动了一个run loop,可是若是你要建立人和网辅助线程,你必须本身手工设置一个run lloop而且启动他。
2.5 中断线程
退出一个线程推荐的方法是让它在它主体入口点正常退出,尽管Cocoa、POSIX和Mutiprocessing Services提供了直接杀死线程的方法。可是使用这些方法是强烈不鼓励的,由于他们阻止了线程自己清理工做。可能照成内存泄露,而且其余线程当前使用的资源可能没有被正确清理干净,以后照成潜在的问题。
若是程序须要中断一个线程,应该设计线程响应取消或退出的消息。
响应取消消息的一个方法是使用 run loop 的输入源来接收这些消息。列表 2-3 显示了该结构的相似代码在你的线程的主体入口里面是怎么样的(该示例显示了主循 环部分,不包括设立一个自动释放池或配置实际的工做步骤)。该示例在 run loop 上面安装了一个自定义的输入源,它能够从其余线程接收消息。关于更多设置输入源 的信息,参阅“配置 Run Loop 源”。执行工做的总和的一部分后,线程运行的 run loop 来查看是否有消息抵达输入源。若是没有,run loop 当即退出,而且循环继续处理 下一个数据块。由于该处理器并无直接的访问 exitNow 局部变量,退出条件是经过 线程的字典来传输的。
Listing 2-3 Checking for an exit condition during a long job
- (void)threadMainRoutine { BOOL moreWorkToDo = YES; BOOL exitNow = NO; NSRunLoop* runLoop = [NSRunLoop currentRunLoop]; // Add the exitNow BOOL to the thread dictionary. NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary]; [threadDict setValue:[NSNumber numberWithBool:exitNow]forKey:@"ThreadShouldExitNow"];  // Install an input source.  [self myInstallCustomInputSource];  while (moreWorkToDo && !exitNow)  {  // Do one chunk of a larger body of work here.// Change the value of the moreWorkToDo Boolean when done.// Run the run loop but timeout immediately if the input source isn't waiting to fire. [runLoop runUntilDate:[NSDate date]]; // Check to see if an input source handler changed the exitNow value. exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue]; } 
第三章 run loop
Run loop 接收输入事件来自两种不一样的来源:输入源(input source)和定时源 (timer source)。输入源传递异步事件,一般消息来自于其余线程或程序。定时源 则传递同步事件,发生在特定时间或者重复的时间间隔。两种源都使用程序的某一特 定的处理例程来处理到达的事件。