进程:是指在系统中正在运行的一个应用程序。
线程:1. 一个进程要想执行任务,就必须要有线程(每一个进程至少要有一个线程)
2. 一个进程(程序)的所有任务都在线程中执行
3. 同一时间,一个线程只能执行一个任务
进程与线程的比较:
1. 线程是CPU调用(执行任务)的最小单位
2. 进程是CPU分配资源和调度的单位
3. 一个程序可以对应多个进程,一个进程中可以有多个线程,但至少要有一个线程
4. 同一个进程内的线程要共享进程的资源
多线程:
一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
多线程的原理:
同一时间,CPU只能处理一条线程,只有一条线程在工作
多线程并发(同时)执行时,其实是CPU快速的在多线程之间调度(切换)
如果CPU调度线程的时间足够快,就造成了多线程同时执行的假象
问:如果线程非常多,会如何?
CPU会在N多线程之间来回调度,会让CPU消耗大量的资源
每条线程调度执行的效率就会降低 并不是线程开的越多越好,通常3-5条线程为好
多线程的优缺点:
优点:1. 能适当的提高线程的执行效率
2. 能适当提高资源利用率
缺点:1. 创建线程是要有开销的,iOS下主要成本包括:内核数据结构(大约在1KB),占空间(子线程
512KB,主线程1MB,也可以使用-setStackSize:设置,但必须是4KB的倍数,而且最小是
16KB),创建线程大概需要90毫秒的时间
2. 如果开启大量的线程,会降低程序的性能
3. 线程越多,CPU在调度线程上的开销就会越大
4. 程序设计更加复杂:比如线程之间的通信,多线程的数据共享
主线程:
一个iOS程序运行后,默认会开启一条线程,称为“主线程”或“UI线程”
主线程的作用:显示、刷新UI界面
处理UI事件(比如点击事件、滚动事件、拖拽事件等)
主线程的使用注意:
别将比较耗时的操作放到主线程中,耗时操作会卡住主线程,严重UI流畅度,给用户一种使用卡顿的体验
多线程安全隐患--互斥锁(专业名词:线程同步)
iOS有三种多线程编程的技术,分别是:
(一)NSThread
(二)Cocoa NSOperation
(三)GCD(全称:Grand Central Dispatch)
这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。
三种方式的优缺点介绍:
)NSThread:
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event{
[self createNewThread3];
}
//1.创建线程 alloc 需要手动来启动线程
- (void)createNewThread1{
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"ABC"];
//启动线程
[thread start];
}
//2.分离子线程,会自动开启线程
- (void)createNewThread2{
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"分离子线程"];
}
//3.开启后台线程
- (void)createNewThread3{
[self performSelectorInBackground:@selector(run:) withObject:@"开启后台线程"];
}
- (void)run:(NSString *)param{
NSLog(@"-----run------%@-----%@",[NSThread currentThread],param);
}
@end
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销
NSThread实现的技术有下面三种:
一般使用cocoa thread 技术。
(二)Cocoa NSOperation的使用
NSOperation是一个抽象类,本身不具备封装操作的能力,必须使用 NSOperation的子类来完成,
方式有两种,一种是用系统提供的两个子类:NSInvocationOperation 和 NSBlockOperation。
另一种是继承NSOperation
如果你也熟悉Java,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一样,NSOperation也是设计用来扩展的,只需继承重写NSOperation的一个方法main。相当与java 中Runnalbe的Run方法。然后把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。
NSOperation和NSOperationQueue实现多线程的步骤:
a). 先将需要执行的操作封装到一个NSOperation对象中,
b). 然后再将NSOperation对象添加到NSOperationQueue队列中,
c). 系统会自动将NSOperationQueue中的NSOperation取出来,
d). 将取出的NSOperation封装的操作放到一条新线程中执行
NSInvocationOperation:(添加多个任务时,都在主线程中执行,不会开辟新的子线程)
- (void)invocationOperation{
//NSInvocationOperation
//1.创建操作,封装任务
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
//2.启动、执行操作
[op start];
}
NSBlockOperation:(执行多个任务时,具备开子线程的能力,也有可能在主线程中执行)
- (void)blockOperation{
//NSBlockOperation
//1.创建操作,封装任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1---%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2---%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3---%@",[NSThread currentThread]);
}];
//追加任务
//注意:如果一个操作中的任务数量大于1,那么会开子线程并发执行任务
//注意:不一定是子线程,有可能是主线程
[op3 addExecutionBlock:^{
NSLog(@"4---%@",[NSThread currentThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"5---%@",[NSThread currentThread]);
}];
[op3 addExecutionBlock:^{
NSLog(@"6---%@",[NSThread currentThread]);
}];
//2.启动、执行操作
[op1 start];
[op2 start];
[op3 start];
}
NSOperationQueue队列:(队列中有多个任务时,是并发状态)
一、 - (void)invocationOperationQueue{
//NSInvocationOperation
//1.创建操作,封装任务
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download3) object:nil];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//将任务添加到队列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
二、- (void)blockOperationQueue{
//1.创建操作,封装任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1---%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2---%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3---%@",[NSThread currentThread]);
}];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//将任务添加到队列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
}
NSOperation的操作依赖(控制执行顺序)
//1.创建操作,封装任务
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1---%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2---%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3---%@",[NSThread currentThread]);
}];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//添加操作依赖
[op1 addDependency:op3]; (操作依赖的执行顺序是2--3--1)
[op3 addDependency:op2]; 注意:不能循环依赖,如发生循环依赖,谁都不会执行
//将任务添加到队列中
[queue addOperation:op1];
[queue addOperation:op2];
[queue addOperation:op3];
NSOperation的操作监听:(当任务执行完毕时,如电影下载完成,可通过监听操作给用户发送下载完成消息通知)
//操作监听
op2.completionBlock = ^{
NSLog(@"----下载完成!----%@",[NSThread currentThread]);
}; 如图所示:
如何控制线程池中的线程数?
队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。
(三)GCD(自动管理)
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。现在的iOS系统都升级到7了,所以不用担心该技术不能使用。
(三)GCD的介绍和使用
介绍:
Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。
GCD有两个核心概念:
任务:执行什么操作
队列:用来存放任务
GCD的使用就两个步骤:
1. 定制任务(确定想做的事情)
2. 将任务添加到队列中(GCD会自动将队列中的任务取出,放到对应的线程中执行 任务的取出遵循队列的FIFO原则:先进先出,后进后出)
GCD的工作原理是:
让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。
一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。
GCD有两个用来执行任务的常用函数:
用同步的方式来执行任务:
参数一:GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行。
参数儿:dispatch_block_t 用来封装任务的
dispatch_sync (dispatch_ queue _t queue, dispatch_block_t block);
用异步的方式来执行任务:
dispatch_async (dispatch_ queue _t queue, dispatch_block_t block);
同步和异步的区别:
同步:只能在当前线程中执行任务,不具备开启新线程的能力
异步:可以在新的线程中执行任务,具备开启新线程的能力
GCD还有一个执行任务的函数:
dispatch_barrier_async (dispatch_ queue _t queue, dispatch_block_t block);
在前面的任务执行结束后,它才执行。而且它后面的任务等它执行完成后才会执行。
这个dispatch queue不是全局的队列
dispatch queue分为下面三种:
Serial (串行队列Serial Dispatch Queue)
又称为private dispatch queues,同时只执行一个任务。一个任务执行完成后再执行下一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
Concurrent(并发队列Concurrent Dispatch Queue)
又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。
并发功能只有在异步函数中才会有效(dispatch_async)
Main dispatch queue
它是全局可用的serial queue,它是在应用程序主线程上执行任务的。
GCD延迟函数:
- (void)delay{
//第一个参数:DISPATCH_TIME_NOW从现在开始计算时间
//第二个参数:要延迟的时间 2.0 GCD时间单位:纳秒
//第三个参数:队列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"GCD---%@",[NSThread currentThread]);
});}
GCD快速迭代
//开子线程和主线程一起完成遍历任务的,任务的执行是并发的
- (void)applyDemo{
//第一个参数:遍历的次数
//第二个参数:队列(并发队列)
//第三个参数:index 索引
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"GCD--- %zd --- %@",index, [NSThread currentThread]);
});
}