上文我们简单的叙述了多线程,那么这篇我们就详细的说一下!
多线程技术方案
PThread
导入头文件
#import <pthread.h>
创建线程
/**
参数
1. 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *
2. 线程属性
3. 线程调用的函数
void * 返回类型
(*) 函数指针
void * 参数类型
4. 运行函数的参数
返回值
- 若线程创建成功,则返回 0
- 若线程创建失败,则返回出错编号
*/
pthread_t threadId =NULL;
char * name ="zhangsan";
NSString * ocName =@"lisi";
int age =19;
// 1> 传递 C 语言字符串
// int result = pthread_create(&threadId, NULL, demo, name);
// 2> 传递 OC 字符串
// int result = pthread_create(&threadId, NULL, demo, (__bridge void *)(ocName));
// 3> 传递基本数据类型intresult = pthread_create(&threadId,NULL, demo, &age);
if(result ==0) {
NSLog(@"成功");
}else{
NSLog(@"失败");
}
线程调用函数
/// 线程调用函数
void * demo(void* param) {
// NSString *name = (__bridge NSString *)(param);
// NSLog(@"%@ %@", [NSThread currentThread], name);
int * age = param;
NSLog(@"%@ %d", [NSThreadcurrentThread], *age);
return NULL;
}
小结
1. 在 C 语言中,没有对象的概念,对象是以结构体的方式来实现的
2. 通常,在 C 语言框架中,对象类型以_t/Ref结尾,而且声明时不需要使用*
3. C 语言中的void *和 OC 中的id是类似的
4. 内存管理
1) 在 OC 中,如果是ARC开发,编译器会在编译时,根据代码结构,自动添加retain/release/autorelease
2) 但是,ARC 只负责管理 OC 部分的内存管理,而不负责 C 语言 代码的内存管理
3) 因此,开发过程中,如果使用的 C 语言框架出现retain/create/copy/new等字样的函数,大多都需要release,否则会出现内存泄漏
4. 在混合开发时,如果在 C 和 OC 之间传递数据,需要使用__bridge进行桥接,桥接的目的就是为了告诉编译器如何管理内存
1) 桥接的添加可以借助 Xcode 的辅助功能添加
2) MRC 中不需要使用桥接
NSThread
创建线程的三种方式
准备线程执行方法
#pragma mark - 线程执行方法
- (void)longOperation:(id)param {
NSLog(@"%@ - %@", [NSThread currentThread], param);
}
alloc/init创建线程
#pragma mark - 创建线程
/// 使用 alloc / init 创建线程
- (void)thread1 {
NSLog(@"before %@", [NSThread currentThread]);
NSThread*thread = [[NSThread alloc] initWithTarget:selfselector:@selector(longOperation:) object:@"hello"];
[thread start];
NSLog(@"after %@", [NSThread currentThread]);
}
代码小结
1. [thread start];执行后,会在另外一个线程执行longOperation:方法
2. 在 OC 中,任何一个方法的代码都是从上向下顺序执行的
3. 同一个方法内的代码,都是在相同线程执行的(block除外)
使用类方法创建线程
/// 使用 NSThread 类方法
- (void)thread2 { [NSThreaddetachNewThreadSelector:@selector(longOperation:) toTarget:self withObject:@(__FUNCTION__)];}
注: detachNewThreadSelector类方法不需要启动,会自动创建线程并执行@selector方法
使用 NSObject 分类方法创建线程
/// 使用 NSObject 分类方法
- (void)thread3 {
[self performSelectorInBackground:@selector(longOperation:) withObject:@(__FUNCTION__)];
}
代码小结
1. performSelectorInBackground是NSObject的分类方法
2. 会自动在后台线程执行@selector方法
3. 没有thread字眼,隐式创建并启动线程
4. 所有NSObject都可以使用此方法,在其他线程执行方法
5. Swift中不支持
NSThread 的 Target
NSThread的实例化方法中的target指的是开启线程后,在线程中执行哪一个对象的@selector方法
准备对象
@interface Person : NSObject
- (void)run;
@end
@implementation Person
- (void)run {
NSLog(@"跑了 5 分钟 %@", [NSThread currentThread]);
}
@end
异步执行对象方法
// 1. 创建 Person 对象
Person *person = [Person new];
// 2. 在异步执行对象方法
// 1> 方式 1
NSThread * t = [[NSThread alloc] initWithTarget:person selector:@selector(run) object:nil];
[t start];
// 2> 方式2
[NSThread detachNewThreadSelector:@selector(run) toTarget:person withObject:nil];
// 3> 方式3
[person performSelectorInBackground:@selector(run) withObject:nil];
代码小结
1. 通过指定不同的target会在后台线程执行该对象的@selector方法
2. 提示:不要看见target就写self
3. performSelectorInBackground可以让方便地在后台线程执行任意NSObject对象的方法
线程的状态
状态说明
新建
1. 实例化线程对象
就绪
1. 向线程对象发送start消息,线程对象被加入可调度线程池等待 CPU 调度
2. detach方法和performSelectorInBackground方法会直接实例化一个线程对象并加入可调度线程池
运行
1. CPU负责调度可调度线程池中线程的执行
2. 线程执行完成之前,状态可能会在就绪和运行之间来回切换
3. 就绪和运行之间的状态变化由 CPU 负责,程序员不能干预
阻塞
1. 当满足某个预定条件时,可以使用休眠或锁阻塞线程执行
2. sleepForTimeInterval:休眠指定时长
3. sleepUntilDate:休眠到指定日期
4. @synchronized(self):互斥锁
死亡
1. 正常死亡
2. 线程执行完毕
3. 非正常死亡
4. 当满足某个条件后,在线程内部中止执行
5. 当满足某个条件后,在主线程中止线程对象
控制线程状态的方法
启动
1. [_thread start]
1) 线程进入就绪状态,当线程执行完毕后自动进入死亡状态
休眠
1. 方法执行过程中,符合某一条件时,可以利用sleep方法让线程进入阻塞状态
1) sleepForTimeInterval从现在起睡多少秒
2) sleepUntilDate从现在起睡到指定的日期
死亡
1. [NSThread exit]
1) 一旦强行终止线程,后续的所有代码都不会被执行
2) 注意:在终止线程之前,应该注意释放之前分配的对象!
取消
1. [_thread cancel]
1) 并不会直接取消线程
2) 只是给线程对象添加isCancelled标记
3) 需要在线程内部的关键代码位置,增加判断,决定是否取消当前线程
代码演练
定义成员变量
@implementation ViewController{
NSThread*_thread;
}
实现touch方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
// 跟踪线程状态
NSLog(@"%@ %zd %zd %zd", _thread, _thread.isFinished, _thread.isCancelled, _thread.isExecuting);
if(_thread ==nil|| _thread.isFinished|| _thread.isCancelled) {
// 1. 实例化线程
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 2. 启动线程,添加到可调度线程池
[_thread start];
}
}
实现调度方法,测试阻塞线程执行
- (void)demo {NSLog(@"开始");// 1. 阻塞到指定日期[NSThreadsleepUntilDate:[NSDatedateWithTimeIntervalSinceNow:1]];for(NSIntegeri =0; i <10; i++) {NSLog(@"%zd %@", i, [NSThreadcurrentThread]);// 1> 阻塞指定时长[NSThreadsleepForTimeInterval:1]; }NSLog(@"over");}
在线程内部直接终止线程
- (void)demo {
NSLog(@"开始");
// 1. 阻塞到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
for(NSInteger i =0; i <10; i++) {
NSLog(@"%zd %@", i, [NSThread currentThread]);
// 1> 阻塞指定时长
[NSThread sleepForTimeInterval:1];
// 2> 当满足某一个条件后,直接终止线程,线程终止后,不会执行后续代码
if(i ==5) {
[NSThread exit];
}
}
NSLog(@"over");
}
在外部终止线程
修改touch方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event {
// 跟踪线程状态
NSLog(@"%@ %zd %zd %zd", _thread, _thread.isFinished, _thread.isCancelled, _thread.isExecuting);
if(_thread ==nil|| _thread.isFinished|| _thread.isCancelled) {
// 1. 实例化线程_thread = [[NSThreadalloc] initWithTarget:selfselector:@selector(demo) object:nil];
// 2. 启动线程,添加到可调度线程池
[_thread start];
}else{
// 线程执行中,直接终止
[_thread cancel];
}
}
运行测试,线程并不会被终止
给线程对象发送cancel消息,只是给线程对象增加的一个标记
是否终止需要在线程调用的代码内部处理
修改demo函数
- (void)demo {
NSLog(@"开始");
// 1. 阻塞到指定日期
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
if([NSThread currentThread].isCancelled) {
NSLog(@"被取消");
return;
}
for(NSInteger i =0; i <10; i++) {
NSLog(@"%zd %@", i, [NSThread currentThread]);
if([NSThread currentThread].isCancelled) {
NSLog(@"被取消");
return;
}
// 1> 阻塞指定时长
[NSThread sleepForTimeInterval:1];
// 2> 当满足某一个条件后,直接终止线程,线程终止后,不会执行后续代码
if(i ==5) {
[NSThreadexit];
}
}
NSLog(@"over");
}
线程的属性
常用属性
1. name- 线程名称
1). 设置线程名称可以当线程执行的方法内部出现异常时,记录异常和当前线程
2. stackSize- 栈区大小
1). 默认情况下,无论是主线程还是子线程,栈区大小都是512K
2). 栈区大小可以设置[NSThread currentThread].stackSize = 1024 * 1024;
3). 必须是4KB的倍数
3. isMainThread- 是否主线程
4. threadPriority- 线程优先级
1). 优先级,是一个浮点数,取值范围从0~1.0
2). 1.0表示优先级最高
3). 0.0表示优先级最低
4). 默认优先级是0.5
5). 优先级高只是保证 CPU 调度的可能性会高
5. qualityOfService- 服务质量(iOS 8.0 推出)
1). NSQualityOfServiceUserInitiated- 用户需要
2). NSQualityOfServiceUtility- 实用工具,用户不需要立即得到结果
3). NSQualityOfServiceBackground- 后台
4). NSQualityOfServiceDefault- 默认,介于用户需要和实用工具之间
5). NSQualityOfServiceUserInteractive- 用户交互,例如绘图或者处理用户事件
关于优先级和服务质量
1. 多线程的目的:是将耗时的操作放在后台,不阻塞主线程和用户的交互!
2. 多线程开发的原则:简单
3. 在开发时,最好不要修改优先级,不要相信用户交互服务质量
4. 内核调度算法在决定该运行哪个线程时,会把线程的优先级作为考量因素
1). 较高优先级的线程会比较低优先级的线程具有更多的运行机会
2).较高优先级不保证你的线程具体执行的时间,只是相比较低优先级的线程,更有可能被调度器选择执行而已
代码
- (void)viewDidLoad {
[superview DidLoad];
if([NSThread currentThread].isMainThread){
NSLog(@"主线程 %zd K", [NSThread currentThread].stackSize/1024);
}
NSThread * t1 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
// 指定线程名称,便于调试
t1.name=@"thread 001";
// 指定线程优先级,CPU 会增加改线程的调度频率
// t1.threadPriority = 1;
// 指定线程服务质量
t1.qualityOfService=NSQualityOfServiceUserInitiated;
[t1 start];
NSThread * t2 = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
t2.name=@"thread 002";
t2.threadPriority=0;
t2.qualityOfService=NSQualityOfServiceBackground;
[t2 start];
}
- (void)demo {
for(NSInteger i =0; i <10; i++) {
NSLog(@"%@ %zd K", [NSThread currentThread], [NSThread currentThread].stackSize/1024);
}
// 模拟线程崩溃
if(![NSThread currentThread].isMainThread) {
int i =10;
int x =0;
NSLog(@"%zd", i / x);
}
}