美文网首页iOS Developer
ios 多线程的故事2

ios 多线程的故事2

作者: Clark_new | 来源:发表于2017-06-14 15:16 被阅读0次

上文我们简单的叙述了多线程,那么这篇我们就详细的说一下!

多线程技术方案

多线程技术方案

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);

    }

}

相关文章

网友评论

    本文标题:ios 多线程的故事2

    本文链接:https://www.haomeiwen.com/subject/vbfdqxtx.html