并发(Concurrency)意味着 同时 可以执行两个或更多的任务。现代操作系统的重要特征之一就是支持并发,哪怕只有一个CPU。
现代系统拥有不止一个CPU,这意味着操作系统可以将不同的任务分发(Dispatch)到不同的CPU上分别执行。
通俗地说,进程 就是 活着 的程序。
注意,程序只有一个,但运行该程序的进程可以不止一个。例如同时挂多个QQ,或游戏的双开。
在单CPU的角度上, 对于存在的多进程,CPU实际上是 来回交替 着执行几个进程中的代码的。所以,严格说起来,并 没有“同时”的概念。
对于多CPU而言,上述说法依然成立,因为进程的数量总是比CPU多,永远不可能真正存在一个进程一个CPU的情况。
与此同时,一个进程中可以至少会一个 线程,每个线程都从一个函数开始,到这个函数执行结束为止。这个函数称为线程的 执行函数。
在没有额外创建线程时,进程中有且仅有一个线程,该线程的执行函数为main函数。一个进程的执行过程,实际上就是其中线程轮流执行的过程。
栈空间 是每个线程独有的,而 堆空间,则是同一个进程中所有线程共用的。在系统中,每个线程都有一个对应的 NSThread 对象。
在任何地方(必定是在一个线程中),使用NSThread类的 + currentThread 类方法 都能获得当前线程对象的引用。
执行main函数的线程,被称为 主线程 。iOS中应用程序的主线程也称 UI 线程 。主线程“天生”具有特殊性:
主线程自动有一个运行循环(run loop) 在iOS中,主线程是界面线程主线程的作用: 显示/刷新UI界面 处理UI事件主线程在执行代码期间,用户无法与界面进行交互。所以,在主线程中执行的代码要短小快速;反而言之,耗时的操作,不要放到主线程中执行。2. NSThread2.1 基本概念创建线程时,创建方被称为 父线程,被创建方被称为 子线程。父线程和子线程并行执行各自的处理,但父线程可以等到子线程执行终止后与其 汇合(join)。另一方面,在线程被创建后,也可以切断父子关系指定它们不会汇合,该操作被成为 分离(detach)。通过NSThread的 + alloc 类方法 及 - initWithTarget: selector: object: 方法 可以实例化出一个NSThread对象,即代表着一个线程,而后通过 - start 方法 让该线程运行。上述操作的“偷懒办法”是创建分离线程,通过NSThread的 + detachNewThreadSelector: toTarget: withObject: 类方法 实例化一个线程,该线程被创建后自动直接运行,不需要再去start。一个线程称自身为 当前线程,以区别于其他线程。使用NSThread的 + currentThread 类方法 可以获得表示当前线程NSThread实例。使用 + mainThread 类方法 能获得表示主线程的NSThread实例。另外,查看当前线程是否是主线程,可以使用NSThread对象的 isMainThread 属性 。通过NSThread的 + exit 类方法 可以终止当前线程的执行。 + sleepForTimeInterval: 类方法 可以是当前线程中断(暂停)若干秒。 使用 + sleepUntilDate: 类方法 可以让线程在指定时刻前一直保持中断(暂停)状态。另外,如果你能获得一个线程的NSThread对象,你可以通过对法发送 - cancel 消息 将它终止。2.2 线程的属性(了解)每个线程都有 优先级,通过 threadPriority 属性 可以访问/设置。该属性是个double类型的值,默认为0.5,1.0为最高优先级。注意,优先级对于系统的调用而言是个建议值,即,系统 不保证 高优先级线程就一定先执行完!通过线程的 name 属性,你可以给线程起个名字。通过线程 stackSize 属性 可以获得/设置栈空间的大小。默认大小为512K。主线程是1M。2.3 线程的状态线程的 状态 的概念源自进程的状态。 线程(进程)的核心状态有三种:runnable、running、blocked runnable状态 是一种万事俱备只欠东风的状态,这里的“东风”指得就是CPU。在新建之后,及 时间片 到期后,线程就是处于这种状态。 只要程序(进程)在运行,不论其中有多少线程,有且仅有一个 线程处于running状态 。线程处于running状态,线程的执行函数就在执行。 如果线程欠缺除了CPU之外的其他原因(如等待用户输入或sleep),线程处于blocked状态。在其阻塞原因消失前,该线程无法执行;其阻塞原因解除后,该线程会转为runnable状态。系统进行的 线程调度 ,就是从所有处于runnable状态的线程中,(带有一定 随机性 地)选择一个线程,让CPU去执行其执行函数,此时该线程处于running状态。 整个过程跟blocked状态的线程没有任何关系。2.4 NSObject中的线程相关方法继承自NSObject的类都会继承得到若干与线程有关的方法。通过 - performSelectorOnMainThread: withObject: waitUntilDone: 方法 让主线程执行当前对象的某个方法,并通过第三个参数指明是否让当前进程等待。通过 - performSelector: onThread: withObject: waitUntilDone: 方法 让指定线程去执行当前对象的某个方法,并通过参数指明是否让当前线程等待。另外,NSObject的 - performSelectorInBackground: withObject: 方法 会新建一个线程,在该线程中执行当前对象的指定方法。2.5. 互斥同一个进程里的多个线程间会共享一些数据,特别是堆上的对象。一个进程中所有线程都使用的是同一个堆空间,栈空间都是各线程独立的。由于多个线程可能会访问同一个数据,容易导致数据的错乱,需要引入 互斥 机制。NSLock 对象 就是作互斥作用的。每一个锁对象都有两种状态:已锁 和 未锁 。任何一个线程可以通过NSLock对象的 - lock 方法 和 - unlock 方法 来改变锁的状态。只有 将锁对象置为已锁状态的线程,才能 将其再次置为未锁状态。注意,如果一个锁对象已经被某一个线程置为已锁状态,其他线程再调用lock方法时,会导致其他线程自身的阻塞。@synchronized语法是NSLock的 语法糖 。2.6.RunLoop运行循环(RunLoop),每一个iOS程序中都有一个RunLoop,在程序的main函数执行之后,Cocoa会创建一个RunLoop。RunLoop的作用有两个a. 保证程序不会退出。b. 监听所有事件。如,触摸、手势、定时器等。3. CGD3.1 基本概念GCD, Grand Central Dispath,一种避免直接使用低级线程接口的替代技术方案。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中。对于上述官方说明,使用Block语法“定义想执行的任务”,通过dispatch_async等函数“追加”赋值在代表“Dispatch Queue”的变量中。Dispatch Queue,是执行处理的等待队列,开发者通过dispatch_async等函数向其中追加Block变量。Disapatch Queue按照追加的顺序(FIFO)执行处理。存在两种Dispatch Queue,一种是 Serial Dispatch Queue(串行队列),另一种是Concurrent Dispatch Queue(并行队列)。一个串行队列的背后 只有一个线程为其服务;一个并行队列的背后,有多个线程为其服务(具体数量由系统决定)。3.2 生成 / 获得 Dispatch Queue通过 dispatch_queue_create 函数 可以生成Dispatch Queue。其 第二个参数 值为 DISPATCH_QUEUE_SERIAL,或者 DISPATCH_QUEUE_CONCURRENT, 分别表示要求生成 Serial Queue 和 Concurrent Queue。第一个参数表示为生成的队列指定名称,该名字在程序崩溃时会显示在日志中,以供分析。如无需要,可以为NULL。生成的Dispath Queue必须由程序员负责释放,哪怕是在ARC环境下也是如此。使用dispatch_release 函数 来释放生成的队列。3.3 同步分发和异步分发不论是选择那种类型的队列,在向队列分发任务时,都有两种分发方式:同步分发,通过 dispatch_sync 函数 异步分发,通过 dispatch_async 函数同步分发和异步分发的区别在于以下两点a. 同步分发会导致当前线程阻塞;而异步分发则不会。b. 同步分发的block在队列中,会阻塞后续block的执行;而异步分发则不会。3.4 “现成的”队列如无特殊需要,一般而言是不需要创建队列的。因为系统为每个程序提前准备好了5个队列。从类型上来分,有4个队列是并发队列,1个队列是串行队列。现成的4个并发队列被成为全局队列(global queue),可以使用dispatch_get_global_queue 函数 获得,并通过其第一个参数(队列优先级)指定选择4个队列中的某一个。另一个线程的队列被成为主队列,它是一个串行队列。通过 dispatch_get_main_queue 函数 能获。主队列 有 特殊性,为主队列“服务”的线程是主线程。在程序的任何地方都可以获得这5个队列。全局队列的优先级DISPATCH_QUEUE_PRIORITY_HIGH DISPATCH_QUEUE_PRIORITY_DEFAULT DISPATCH_QUEUE_PRIORITY_LOW DISPATCH_QUEUE_PRIORITY_BACKGROUND优先级 是一个建议性参数,系统不保证高优先级的就一定先执行完。注意,除了主队列之外,没有现成的串行队列。如果要使用,必须自己创建。3.5 参照表有些地方会有如下表格,但是该表格描述信息不准确(错误)。分发方式同步(sync)同步(async)全局并行队列没有开启新线程;串行执行任务有开启新线程;并行执行任务手动创建串行队列没有开启新线程;串行执行任务有开启新线程;串行执行任务主队列会死锁没有开启新线程;串行执行任务3.6 线程间的通讯在iOS中,要求对UI界面的操作代码,必须放在主队列中(由主线程)执行。否则,可能会出现界面刷新不及时的现象。反过来说,凡是和更行界面UI操作无关的代码(特别是耗时的代码),不要由主线程执行。否则,会出现“某段时间内”界面操作无反应的现象。所以,程序中往往会有这样的逻辑需求:在子线程中执行耗时操作(或运算),而后将结果显示在界面上。实现这种要求的代码已经成了一种固定“套路”1. 2. 3. 4. 5. 6. 7. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 执行耗时操作或运算... dispatch_async(dispatch_get_main_queue(), ^{ // 回到主线程,执行UI刷新操作; });});3.6. 延迟执行常见的延迟执行有两种:a. 通过NSObject的 - performSelector: withObject: afterDelay: 方法b. 使用GCD函数 dispatch_after 函数dispatch_after( )函数的第一个参数是一个表示一个延迟时间的概念,其类型为dispatch_time_t,你可以通过 dispatch_time 函数 方便地构造一个时间概念。通过第二个参数和第三个参数,你可以指定一个队列,及其需要执行的代码块。3.7. 调度组你可能需要实现这样的逻辑:在追加到Dispatch Queue中的多个任务全部结束后,执行某个操作。如果该 队列是个串行队列,该逻辑非常好实现,但如果该队列是个并行队列,情况就变得有些复杂。在此种情况下使用 Dispatch Group 。你可以通过 dispatch_group_create 函数,创建一个 调度组 。再通过 dispatch_group_async 函数 将一个或多个队列及其对应执行代码添加至调度组中。无论向什么样的队列中追加任务,使用Dispatch Group都可监视这些处理执行的结束。通过dispatch_group_notify 函数 指明,当组中所有任务完成后,在哪个队列上执行什么样的任务。另外,也可使用 dispatch_group_wait 函数 阻塞当前线程,等待组中所有任务执行结束。记得使用 dispatch_release 函数 释放调度组。3.8. 一次性执行dispatch_once 函数 代码块中的代码只会执行一次。其第一个参数是个结果参数。4. NSOperation4.1 基本概念NSOperation是对NSThread的封装。每个 NSOperation 对象 表示一个要执行的任务。而 NSOperationQueue 类似GCD中的Dispatch Queue。要使用NSOperation,必须对其子类化。不过在自定义子类继承NSOperation之前,系统中已有两个NSOperation的子类可直接使用: NSInvocationOperation 类 和NSBlockOperation 类4.2 NSInvocationOperation在使用NSInvocationOperation 的 - initWithTarget: selector: object: 方法 初始化一个NSInvocationOperation之后,可以通过其 - start 方法 在当前线程中立即运行它。单独地使用NSInvocationOperation并没有太多实际意义,更多的是将其放入NSOperationQueue(代表队列)中。通过NSOperationQueue对象的 - addOperation: 方法 实现添加操作。只要将NSOperation添加到队列中,会 自动以异步方式执行,类似于GCD中的并发队列。NSOperation和NSOperationQueue的执行模式都是:并发队列 + 异步执行。通过NSOperationQueue的 + mainQueue 类方法 得到的NSOperationQueue对象代表主队列。4.3 NSBlockOperation通过NSBlockOperation的 + blockOperationWithBlock: 类方法 可创建一个NSBlockOperation对象,随后可将其添加入NSOperationQueue中。使用NSOperationQueue的 - addOperationWithBlock: 方法 也可实现相同功能。4.4 线程间通信1. 2. 3. 4. 5. 6. 7. 8. 9. NSOperationQueue *q = [[NSOperationQueue alloc] init];[q addOperationWithBlock:^{ NSLog(@\"耗时操作 ...\"); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@\"更新UI ...\"); }];}];4.5 其他通过NSOperation对象的 - addDependency: 方法 能够为两个操作指定依赖关系,即 消息接受者对象 必定在 参数对象 之后执行。 解除两者依赖关系使用 – removeDependency: 方法 。依赖关系的双方,可以在不同的队列中。注意,不要出现循环依赖。通过NSOperationQueue对象的 maxConcurrentOperationCount 属性 可以查询 / 设置为NSOperationQueue的最大并发数,即同一时刻最多有多少个operation在执行。通过NSOperationQueue对象的 suspended 属性 可以挂起/恢复队列的执行,即队列中还未执行的operation暂不执行。不过正在执行中的operation会继续执行,直至完成。挂起的是队列,而非线程。补充,CGD中也有相应地处理方式:dispatch_suspend 函数 和 dispatch_resume 函数调用NSOperationQueue对象的 - cancelAllOperations 方法 可以取消队列中还未执行的所有操作。注意是 未执行 的任务,正在执行的任务不会被取消。相较于GCD而言,NSOperation无法实现一次性执行、延迟执行和调度组。
因篇幅问题不能全部显示,请点此查看更多更全内容