1.什么是进程?
进程是指在一个系统内正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。比如同时打开两个应用支付宝和微信,系统就会分别启动2个进程。
2.什么是线程?
1个进程如果想要执行任务,必须要有线程(每一个进程至少要有一个线程)
线程是进程的基本执行单元,一个进程(程序)的所有任务必须要在线程中执行
3.线程的串行
一个线程中的任务的执行是串行的,如果要在一个线程中执行多个任务,只能一个一个地桉顺序执行;换句话说,在同一时间内,一个线程只能执行一个任务。
4.什么是多线程
一个进程中可以开启多条线程,每条线程并行(同时)执行不同的任务
优点:多线程可以提供程序的执行效率
5.多线程的原理
同一时间,CPU只能处理一条线程,只有一条线程在工作(执行),当多线程并发(同时)执行,实质也就是CPU快速地在多条线程之间调度(切换)
如果CPU调度线程的时间足够快,就会造成了多线程并发执行的假象。如果线程非常的多,CPU将会在N多线程之间调度,CPU会被活活累死,消耗大量的CPU资源,这样也就导致了每条线程被调度执行的频次会降低,即线程的执行效率降低
6.多线程的优点:
不仅能适当提高程序的执行效率,还能提高资源利用率,如CPU、内存的利用率
7.多线程的缺点:
开启多线程需要占用一定的内存空间:默认情况下,主线程占用1M,子线程占用512KB,如果开启大量的线程,或占用大量的内存空间,降低程序的性能。如果线程的数量很多,不止会使CPU在调度线程上的开销增大,也会使得程序设计更加复杂:如线程间的通信和数据共享等。
8.多线程的应用:
主线程:一个iOS程序运行后,默认会开启一条线程,这条线程就被称为"主线程"和"UI线程"
主线程的作用:显示\刷新UI界面以及处理UI事件(如:点击事件、滚动事件、拖拽事件等)
主线程的使用注意事项:不要把比较耗时的操作放到主线程中。因为耗时的操作会卡住主线程,对UI的流畅度造成严重的影响,给用户一种"卡"的坏体验
//8.1多线程的安全隐患
//8.1.1资源共享:
一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源。如多个线程访问同一个对象或者同一个变量,再或者是同一个文件。当多个线程访问同一块资源时,很容易造成引发数据错乱和数据安全的问题
8.1.2-----解决办法
互斥锁使用格式---
@synchronized (锁对象) {
//需要锁定的代码
}
注意:锁定一份代码只用一把锁,用多把锁是无效的
互斥锁的优缺点:
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
互斥锁使用的前提是:多条线程去抢夺同一块资源
9.创建线程
1.一个NSThread对象就代表一条线程,
1.1->创建和启动线程
//创建线程
NSThread* thread = [[NSThread alloc]initWithTarget:self selector:@selector(initThread) object:nil];
//启动线程
[thread start];
//特点:线程一启动,就会在线程thread中执行self的initThread方法
//2--主线程的相关用法:
//2.1-------------是否为主线程
// @property (readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
//thread.isMainThread
//2.2-------------是否为主线程
// @property (class, readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
//属性用法 [NSThread isMainThread];
//2.3-------------获得主线程
// @property (class, readonly, strong) NSThread *mainThread NS_AVAILABLE(10_5, 2_0);
//属性用法[NSThread mainThread];
//2.4---线程的调度优先级:调度优先级的取值范围是0.0~1.0,默认0.5,值越大,优先级越高
// + (double)threadPriority;
// + (BOOL)setThreadPriority:(double)p;
//设置线程的名字
// 2.5--- @property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
//3-----其他创建线程的方式
//3.1----创建线程后自动启动线程
//+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
//3.2----隐式创建并启动线程
// [self performSelectorInBackground:@selector(initThread) withObject:nil];
//上述2种创建线程的优缺点:优点是简单快捷,缺点是无法对线程进行更详细的设置。
当前流程继续performSelector关于内存管理的执行原理是这样的执行 [self performSelector:@selector(method1:) withObject:self.tableLayer afterDelay:3]; 的时候,系统会将tableLayer的引用计数加1,执行完这个方法时,还会将tableLayer的引用计数减1,由于延迟这时tableLayer的引用计数没有减少到0,也就导致了切换场景dealloc方法没有被调用,出现了内存泄露。
利用如下函数:
[NSObject cancelPreviousPerformRequestsWithTarget:self]
当然你也可以一个一个得这样用:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(method1:) object:nil]
加上了这个以后,顺利地执行了dealloc方法
4. 线程间的通信
4.1---线程间通信的含义:在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
4.2---线程间通信的体现:一个线程给另一个线程传递数据或者一个线程执行完之后去执行另一个线程
4.3---- 线程间通信的常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
4.4---从子线程回到主线程
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执⾏耗时的异步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执⾏UI刷新操作
});
});
或者
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执⾏耗时的异步操作...
// 回到主线程,执⾏UI刷新操作
[self.imageView performSelectorOnMainThread:@selector(方法) withObject:image waitUntilDone:NO];
});
5.GCD:Grand Central Dispatch
5.1--GCD是纯C语言,提供了非常强大的函数
5.2---GCD的优势:
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
5.3-------
GCD的两个核心概念:
5.3.1--任务:执行什么操作
5.3.2--队列:用来存放任务
GCD的使用就两个步骤:
1->定制任务
2->确定想要做的事情
将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行
重点:任务的取出遵循队列的FIFO原则:先进先出,后进后出
5.4---执行任务
5.4.1----GCD中有两个用来执行任务的函数。特别说明:把右边的参数(任务-<#^(void)block#>)提交给左边的参数(队列:<#dispatch_queue_t _Nonnull queue#>)进行执行
第一种:用同步的方式执行任务 dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
参数说明:queue:队列 block:任务
第二种:用异步的方式执行任务dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
5.4.2---同步和异步的区别
同步:在当前线程中执行任务,其并不具备开启新线程的能力
异步:在另一条线程中执行任务,它具备了开启新线程的能力
5.5---队列
GCD的队列类型可以分为两种类型:
1->并发队列:可以让多个任务并发(同时)执行(会自动开启多个线程同时执行任务),并发功能只有在异步:dispatch_async函数下才是有效的
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags); // 此参数暂时无用,用0即可
示例:
这个参数是留给以后用的,暂时用不上,传个0。
第一个参数为优先级,这里选择默认的。获取一个全局的默认优先级的并发队列。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获得全局并发队列
说明:全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
2->串行队列:让任务一个接着一个的执行,即一个任务执行完了,再去执行另一个任务
GCD中获得串行有两种方法:
1.1->使用dispatch_queue_create函数创建串行队列
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); // 队列名称, 队列属性,一般用NULL即可
队列名称的作用:
将来调试的时候,可以看得出任务是在哪个队列中执行的。
示例:
dispatch_queue_t queue = dispatch_queue_create("wendingding", NULL); // 创建
dispatch_release(queue); // 非ARC需要释放手动创建的队列
1.2-> 使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
使用dispatch_get_main_queue()获得主队列
示例:dispatch_queue_t queue = dispatch_get_main_queue();
小结
说明:同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。
//iOS的延时操作
/*
iOS常见的延时操作有两种方法:
1.调用NSObject的方法: [self performSelector:@selector(initThread) withObject:nil afterDelay:2.0];
2秒后调用self的initThread方法,该方法在那个线程调用,那么run就在哪个线程执行(当前线程),通常是主线程。
2.使用GCD函数
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//2秒后执行这里
});
如果队列是主队列,那么就在主线程执行,如果队列是并发队列,那么会新开启一个线程,在子线程中执行。
/*
NSOperation
1.---- NSOperation的作用:配合使用NSOperation和NSOperationQueue也能实现多线程编程
其实现多线程的具体步骤:
1.1->将要执行的操作封装到一个NSOperation对象中
1.2->将NSOperation对象添加到NSOperationQueue中
1.3->系统会自动将NSOperationQueue中的NSOperation取出来
1.4->将取出的NSOperation封装的操作放到一条新线程中执行
2----NSOperation的子类
NSOperation是一个抽象类,并不具备封装操作的能力,一定要使用他的子类才可以
使用NSOperationi子类的方式有三种:
(1)NSInvocationOperation
(2)NSBlockOperation
(3)自定义子类继承NSOperation,实现内部相应的⽅法
3.---创建操作对象,封装要执行的任务
3.1->NSInvocationOperation
NSInvocationOperation* operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(initThread) object:nil];
//执行操作
[operation start];
//说明:一旦执行操作,就会调用target的initThread方法
重点:操作对象默认在主线程中执行,只有添加到队列中才会开启新的线程。即默认情况下,如果操作没有放到队列中queue中,都是同步执行。只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
3.2->--NSBlockOperation
//封装操作
//创建NSBlockOperation操作对象
NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
}];
//添加操作
[operation addExecutionBlock:^{
}];
//开启执行操作
[operation start];
//重点:只要NSBlockOperation封装的操作数大于一,就会异步执行操作
4.NSOperationQueue
NSOperationQueue的作用:NSOperation可以调用start方法来执行任务,但默认是同步执行的,如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作,自动开启线程
NSBlockOperation* operation = [NSBlockOperation blockOperationWithBlock:^{
}];
//添加操作
[operation addExecutionBlock:^{
}];
//创建NSOperationQueue
NSOperationQueue* queue = [[NSOperationQueue alloc]init];
//把操作添加到队列中
//第一种添加方式:
[queue addOperation:operation];
//第二种添加方式:
[queue addOperationWithBlock:^{
}];
重点:系统自动将NSOperationqueue中的NSOperation对象取出,将其封装的操作放到一条新的线程中执行。
NSOperation基本操作
1.->并发数:同时执行的任务数,如果同时开启3个线程执行3个任务,那并发数就是3了
2-> 最大并发数:同一时间最多只能执行的任务个数
3-> 最大并发数的相关方法
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
特别说明:如果没有设置最大并发数,那么并发的个数会有系统内存和CPU决定,
温馨提示:最大并发数不要乱写,最好5以内,一般以2~3为宜,因为虽然任务是在子线程进行处理的,但是CPU处理这些过多的子线程可能会影响UI,让UI变卡。
队列的取消,暂停和恢复
1->取消队列的所有操作
- (void)cancelAllOperations;
提⽰:也可以调用NSOperation的- (void)cancel⽅法取消单个操作
2->暂停和恢复队列
- (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
- (BOOL)isSuspended; //当前状态
3->暂停和恢复的适用场合:在tableview界面,开线程下载远程的网络界面,对UI会有影响,使用户体验变差。那么这种情况,就可以设置在用户操作UI(如滚动屏幕)的时候,暂停队列(不是取消队列),停止滚动的时候,恢复队列。
操作优先级
1->设置NSOperation在queue中的优先级,可以改变操作的执⾏优先级
- (NSOperationQueuePriority)queuePriority;
- (void)setQueuePriority:(NSOperationQueuePriority)p;
(2)优先级的取值
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
说明:优先级高的任务,调用的几率会更大。
操作依赖
NSOperation之间可以设置依赖来保证执行顺序,⽐如一定要让操作A执行完后,才能执行操作B,可以像下面这么写
[operationB addDependency:operationA]; // 操作B依赖于操作
但是不能循环依赖
重点:任务添加的顺序并不能够决定执行顺序,执行的顺序取决于依赖。使用Operation的目的就是为了让开发人员不再关心线程。
操作的监听:
可以监听一个操作的执行完毕
- (void (^)(void))completionBlock;
- (void)setCompletionBlock:(void (^)(void))block;
第一种方式:可以直接跟在任务后面编写需要完成的操作,如这里在下载图片后,紧跟着下载第二张图片。但是这种写法有的时候把两个不相关的操作写到了一个代码块中,代码的可阅读性不强。
第二种方式: 在上一个任务执行完后,会执行operation.completionBlock=^{}代码段,且是在当前线程执行。
*/