作为一个开发人员, 有两个词无论是工作中还是面试中, 都会经常听见, 被问及:"进程""线程"。 在开始了解多线程之前, 先来了解一下这二者的关系和区别: 简单点说, 进程是一个有独立地址空间的, 而线程只是一个进程中执行任务的一个路径, 这是二者有本质的区别, 可以说他们是不同的操作系统资源的管理方式。
做iOS开发,很少会涉及到进程。我们一般会做的就是进程间通信,从当前App跳转到第三方App,例如“QQ”、“微信”或指定App。我们可能会在跳转过程中传递一些参数,在第三方App上做出相应的操作,这就是我目前涉及到的跟进程有关的开发。这里我主要还是想讲的是线程,因为,相比之下,线程在我们开发过程中还是尤为重要的。
线程
可以说,一个进程是由一个或多个线程组成的,进程负责调度多条线程, 真正执行任务,负责代码执行的是线程。
在程序被启动的时候,系统的主线程就被创建,直到程序完全退出,这个线程都会存在。他用来执行main函数。这时的主线程要完成所有的操作,网络请求、UI展示、动画等等。在只有主线程的情况下这些任务都要按顺序一个一个执行, 无法并发执行。所以,我们需要多线程来协助主线程完成这个并发执行的任务。
iOS多线程
多线程很好理解,有多条线程在执行任务。
它其实是针对单核的CPU设计的, 因为单核的同一时间只能执行一个任务, 但是我们可以利用多线程,让CPU快速的在多个线程之间切换,从而给我们一种多个任务在同时进行的错觉。在多核的CPU中, 是真实的达到了, 多个线程同时执行任务。
1. 多线程优缺点
优点:① 适当的提高代码执行效率
② 适当提高资源利用率
缺点:① 开启线程过多,会占用大量内存空间,所以要适度
② 程序设计更复杂,需要考虑诸如线程安全的问题
2. iOS中的多线程
iOS中多线程主要有四种:NSThread、NSObject、NSOperationQueue、GCD
2.1 NSTread 线程类
优点:1. 轻量级
2.可以快速创建子线程,并对子线程有控制权
缺点:1. 需要手动管理线程的生命周期
2.要手动开启子线程、基本信息要手动设置
2.1.1 NSTread的创建
NSTread的创建有三种方式, 后两者相当于对第一种进行了封装,我们不再可以设置线程的基本信息
基本的创建方式:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTask) object:nil];
thread.name = @"线程类";
[thread start];
快速创建,并自动开启:
[NSThread detachNewThreadSelector:@selector(threadTask) toTarget:self withObject:nil];
隐式创建,并自动开启:
[self performSelectorInBackground:@selector(threadTask) withObject:nil];
这里面线程我让他执行threadTask里面的任务, 里面模拟了线程卡死代码,并看看当前是在哪执任务
- (void)threadTask {
if ([NSThread isMainThread]) {
NSLog(@"当前线程 是 主线程,是%@", [NSThread currentThread]);
} else {
NSLog(@"当前线程 非 主线程,是%@", [NSThread currentThread]);
}
NSInteger num = 0;
for (NSInteger i = 0; i < 1000000000; i++) {
num++;
}
NSLog(@"%ld", num);
[self performSelector:@selector(reloadUIView) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES];
}
这里面用到了几个线程相关的方法:
isMainThread : 查看当前线程是否是主线程(0:非 / 1:是)
currentThread: 查单当前线程
mainThread: 获得主线程
线程间通信:
在某个线程或主线程执行某个任务
-(void)performSelector:(SEL)aSelectoron Thread:(NSThread*)thread withObject:(id)arg waitUntilDone:(BOOL)wait;
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
2.2 GCD
GCD是iOS开发中应用最为广泛的多线程开发技术,使用得最简单。GCD是苹果公司自己开发的,基于C语言写的,提供了非常强大的函数。
想学懂GCD,不得不先了解一下任务和队列两个核心概念
2.2.1 任务和队列
一、概念:
任务:执行的操作,通常是我们代码中的方法
队列:可以理解为用来存放任务的列表
二、分类:
任务的种类(是否开辟新的线程):
同步执行(不开辟新线程),异步执行(开辟新线程)
队列的种类(任务的执行方式):
并行队列(多个任务可同时执行),串行队列(任务按顺序一个一个执行)
三、组合:
同步执行+并行队列
同步执行+串行队列
异步执行+并行队列
异步执行+串行队列
还有一种队列叫主队列,是串行。加上这种队列,又有两种新组合方式
同步执行+主队列
异步执行+主队列
2.2.2 GCD的使用步骤
1->创建队列:
并行队列:
dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);
串行队列:
dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);
主队列:
dispatch_queue_t queue = dispatch_get_main_queue();
全局队列:
dispatch_queue_t globa = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2->创建任务:
同步执行:
dispatch_sync(queue, ^{
});
异步执行:
dispatch_async(queue, ^{
});
2.2.3 GCD死锁
GCD死锁的问题我也是绕了好久才绕明白。这里来列举几个会造成死锁的案例:
案例1. 主队列+同步执行
// 同步主队列
- (void)syncMainQueue {
NSLog(@"~~~1~~~");//任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"~~~2~~~");//任务2
});
NSLog(@"~~~3~~~");//任务3
}
执行结果:
分析:进入到方法中,首先执行任务1,接下来遇到同步线程的任务2,队列是先进先出的原则,由于三个任务全都是在主队列上,同步的任务2实际上是插在了任务3的后面,但是任务3却要等任务2执行完毕之后才会执行,这样就形成了任务2和任务3互等的状态,无法往下执行,卡死在这里。
解决:自定义串行队列,配合同步线程
// 同步串行
- (void)syncSerial {
NSLog(@"~~~1~~~");
dispatch_sync(dispatch_queue_create("同步串行", DISPATCH_QUEUE_SERIAL), ^{
NSLog(@"~~~2~~~");
});
NSLog(@"~~~3~~~");
}
执行结果:
分析:
主队列也是串行队列,同样是同步线程,但结果却是不一样的。造成执行结果不同的唯一原因就是任务2是执行在一个不同于任务1和任务3的队列中。
来看一下执行顺序,首先执行任务1,接着遇到同步线程的任务2,在任务2执行完毕之后,去执行任务3。由于不是在一个队列中,任务2是排在了自定义的队列的第一个位置, 不用等任务3执行完就可以去执行。、
案例2:在自定义队列中同步执行
- (void)asyncAndSync {
NSLog(@"~~~1~~~");
dispatch_queue_t queue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"~~~2~~~");
dispatch_sync(queue, ^{
NSLog(@"~~~3~~~");
});
NSLog(@"~~~4~~~");
});
NSLog(@"~~~5~~~");
}
执行结果:
分析:
首先看看5个任务都是在哪个队列执行的 任务1-主队列、任务2-自定义队列、任务3-自定义队列、任务4-自定义队列、任务5-主队列。
再来看看任务的执行顺序:首先执行任务1;接着遇到异步任务,暂时跳过,执行任务5;接着进入到异步任务内,执行任务2;接下来遇到同步任务3,但是任务3,任务2,任务4都是执行在自定义的队列里,所以他排在了任务4后,但是任务4还在等同步队列执行完才执行, 所以两者进入了互等状态,卡死在这里。
2.2.4 GCD对比
2.3 NSOperation 操作类
单独的NSOperation对象是不能进行多线程编辑的,它需要配合NSOperationQueue(操作队列)来实现多线程
首先我们应该知道,NSOperation是一个抽象类, 它本事并不具备封装操作的能力, 所以我们必须创建NSOperation的子类
2.3.1 创建NSOperation
NSOperation的子类有三种:NSInvocationOperation、NSBlockOperation和自定义的继承于NSOperation的类。
2.3.2 NSOperation对象的创建与使用
1-> NSInvocationOperation
- (void)invocationOperation {
NSInvocationOperation *operation_1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logNum) object:nil];
[operation_1 start];
NSInvocationOperation *operation_2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logNum) object:nil];
[operation_2 start];
}
2-> NSBlockOperation
这里创建NSBlockOperation对象的方法:
+(id)blockOperationWithBlock:(void(^)(void))block;
添加操作:
-(void)addExecutionBlock:(void(^)(void))block;
- (void)blockOperation {
NSBlockOperation *operation_1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"~~~1.%@~~~", [NSThread currentThread]);
}];
[operation_1 addExecutionBlock:^{
NSLog(@"~~~2.%@~~~", [NSThread currentThread]);
}];
[operation_1 addExecutionBlock:^{
NSLog(@"~~~3.%@~~~", [NSThread currentThread]);
}];
[operation_1 addExecutionBlock:^{
NSLog(@"~~~4.%@~~~", [NSThread currentThread]);
}];
[operation_1 addExecutionBlock:^{
NSLog(@"~~~5.%@~~~", [NSThread currentThread]);
}];
[operation_1 start];
}
执行结果:
3->自定义类
自定义的继承于NSOperation的类需要重写main方法
2.3.3 NSOperationQueue
// 创建队列
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
// 设置最大并发数(同时可处理的线程的个数)
[queue setMaxConcurrentOperationCount:2];
// 创建任务
MyOperation *op1 = [[MyOperation alloc] init];
MyOperation *op2 = [[MyOperation alloc] init];
// 添加到队列
[queue addOperation:op1];
[queue addOperation:op2];