您的位置:首页 > 移动开发 > IOS开发

iOS多线程编程--NSThread

2016-06-14 23:39 501 查看

前言:

OS 支持多个层次的多线程 编程,层次越高的抽象程度越高,使用起来也越方便,对于开发者来说,推荐使用更方便的GCD和NSOperation来进行多线程开发。

但是本文主要讲解的是NSThread的使用,通过NSThread可以相对深入理解多线程的原理。

Thread 是这三种范式里面相对轻量级的,但也是使用起来最负责的,你需要自己管理线程的生命周期,线程间的同步问题。

线程共享同一应用程序的部分内存空间,它们拥有对数据相同的访问权限,你得协调多个线程对同一数据的访问,一般做法是在访问之前加锁,这会导致一定的性能开销。

在 iOS 中我们可以使用多种形式的 thread:

1、Cocoa threads:
使用NSThread,直接从NSObject的类方法performSelectorInBackground:withObject: 来创建一个线程。
如果你选择thread来实现多线程,那么NSThread就是官方推荐优先选用的方式。

2、POSIX threads: 基于 C 语言的一个多线程库。


下面我们先来看看 NSThread 多线程的使用。

从线程创建与启动、线程的同步与锁、线程的交互、线程池等等四个方面来详解多线程。

1、优点:NSThread比其他两种多线程方案较轻量级,更直观地控制线程对象

2、缺点:需要自己管理线程的生命周期,线程同步。
线程同步对数据的加锁会有一定的系统开销。


了解GCD点击这里

了解NSOperation点击这里

一、线程的创建和启动

1、动态方法

//声明
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;

// 初始化线程
NSThread *thread =
[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];  //线程执行的方法,这个selector最多只能接收一个参数

// 设置线程的优先级(0.0 - 1.0,1.0最高级)
thread.threadPriority = 1;

// 开启线程
[thread start];     //这种方式创建,需要手动启动线程


2、静态方法

//声明
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;

[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];  // 调用完毕后,会马上创建并开启新线程


3、隐式创建方法

[self performSelectorInBackground:@selector(run) withObject:nil];


4、区别

第 1 种方式会直接创建线程,并且开始运行线程,而且无需为线程的清理负责;

第 2 种方式是创建线程对象后,需要手动启动线程,在运行线程操作前,可以对线程进行配置,比如设置 stack 大小,线程的优先级。

二、线程的操作方法

1、线程的获取

//返回当前线程
NSThread *current = [NSThread currentThread];

//返回主线程
NSThread *mainT = [NSThread mainThread];


2、线程的判断

// 判断是否为多线程
+ (BOOL)isMultiThreaded;

// 判断当前线程是否为主线程
- (BOOL)isMainThread;
+ (BOOL)isMainThread;


2、线程的配置

// 线程优先级
+ (double)threadPriority ;
+ (BOOL)setThreadPriority:(double)p ;

// 线程函数地址
+ (NSArray *)callStackReturnAddresses;

// 线程堆栈
- (NSUInteger)stackSize;
- (void)setStackSize:(NSUInteger)s;

// 设置与返回线程名称
- (void)setName:(NSString *)n;
- (NSString *)name;


3、线程的暂停、取消

//休眠
NSDate *date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]];
[NSThread sleepUntilDate:date];

// 暂停2s
[NSThread sleepForTimeInterval:2];

//  退出线程
+ (void)exit;

// 取消操作
- (void)cancel;

// 线程启动
- (void)start;

// 线程执行入口
- (void)main;

// 是否在执行
- (BOOL)isExecuting;

// 是否已经结束
- (BOOL)isFinished;

// 是否取消的
- (BOOL)isCancelled;


三、线程间的通信

1、在主线程执行操作

[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];  //waitUntilDone是指是否等到主线程把方法执行完了,这个performSelector方法才返回。

//指定线程的run loop 执行模式
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES modes:NSDefaultRunLoopMode];


2、在指定线程上执行操作

这些performSelector形式的方法,都需要对方线程的RunLoop处于开启状态,因为这些方法实质是runloop的输入源,把消息发送给对方线程的runloop,然后对方从runloop里面获取消息,才去执行方法。

主线程的runloop是默认开启的,副线程的runloop是默认构建,但是需要手动开启。

了解RunLoop点击这里

[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];

//指定线程的run loop 执行模式
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES modes:NSDefaultRunLoopMode];


3、在当前线程执行操作

[self performSelector:@selector(run) withObject:nil];

//指定线程的run loop 执行模式
[self performSelector:@selector(run) withObject:nil inModes:NSDefaultRunLoopMode];


4、线程的关闭

需要关闭某个线程,可以给这个线程发消息使其关闭:

- (void)killThread {
[self performSelector:@selector(exitThread:) onThread:_thread1 withObject:_thread1 waitUntilDone:NO];
}

-(void)exitThread:(NSThread *)thread {
[NSThread exit];
}


或者通过下面的方法使其关闭:

-(void )threadOneMethod{
//前面写线程需要执行任务的代码,最后进入runloop循环,保持线程不结束同时保持接受消息。
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning  ){//shouldKeepRunning判断是否继续进行循环,如果为NO,就会停止循环,然后继续向下运行,线程自然结束
NSLog(@"looprun");
[theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
};
NSLog(@"thread1 end");
}

BOOL shouldKeepRunning = YES;//一个全局的BOOL类型变量

- (void)killThread {
shouldKeepRunning = NO;
}


这里是通过一个全局变量的改变来控制线程的继续还是结束。但是有个小问题是,当线程的runloop接受了外来的输入源之后,例如其他线程调用:

[self performSelector:@selector(timerFire) onThread:_thread1 withObject:nil waitUntilDone:NO];


在这个线程运行,runloop接受到消息后会阻塞在方法[theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]里面,也即是说while循环不会继续向下一个循环进行,那么改变shouldKeepRunning就不能马上得到反馈,所以需要使用:

BOOL shouldKeepRunning = YES;
- (void)killThread {
[self performSelector:@selector(exitThread:) onThread:_thread1 withObject:nil waitUntilDone:NO];
}

-(void)exitThread:(NSThread *)thread{
shouldKeepRunning = NO;
}


这样就是给要关闭的线程发消息,会立刻唤醒目标线程的runloop,因为[theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]方法的特性是在接触到输入源后方法立刻返回,这样while循环就会立刻进入进入下一个循环,也就会进行循环条件的判断,然后因为shouldKeepRunning变为NO了,就会退出循环,然后线程结束。

5、取消发送给当前 线程 的某个消息

cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:


四、多线程的使用举例

对于使用线程的一些建议:

1、当我们需要中途停止线程时,我们不应该调用exit方法,而是调用cancel方法。

因为,如果我们直接调用exit方法的话,线程是直接退出,而没有机会去执行清理操作,可能会产生内存泄漏!

2、我们必须要清楚这么一个现象!

当线程在执行过程中,如果被sleepForTimeInterval后,线程将会被进入休眠。

那么在它休眠期间又被cancel后,事实上,线程在醒来后,任然会执行完它的操作。

// 线程执行
- (void) threadEntryPoint{
@autoreleasepool {
NSLog(@"Thread Entry Point");
while ([[NSThread currentThread] isCancelled] == NO){
[NSThread sleepForTimeInterval:10];
NSLog(@"Thread Loop");
}
NSLog(@"Thread Finished");
}
}

// 停止线程
- (void) stopThread{
NSLog(@"Cancelling the Thread");
[self.myThread cancel];
NSLog(@"Releasing the thread");
self.myThread = nil;
}

调用:
- (void)viewDidAppear:(BOOL)animated{
// 创建线程
self.myThread = [[NSThread alloc]
initWithTarget:self
selector:@selector(threadEntryPoint)
object:nil];

// 开启线程
[self.myThread start];

// 让线程3秒后取消
[self performSelector:@selector(stopThread) withObject:nil
afterDelay:3.0f];
}


输出:
Thread Entry Point
Cancelling the Thread
Releasing the thread
Thread Loop
Thread Finished


分析1:

注意,最后还是输出了 “Thread Loop”这一句,我明明调用了[NSThread sleepForTimeInterval:10]; 方法让线程进入休眠状态。

并且让线程已经执行了stopThread方法中的[self.myThread cancel];方法把线程给取消了。

但是,线程在被唤醒后,任然执行了后面的代码,输出了 “Thread Loop”这一句!

只有改良的办法:多加一层判断!!!

- (void) threadEntryPoint{
@autoreleasepool {
NSLog(@"Thread Entry Point");
while ([[NSThread currentThread] isCancelled] == NO){
[NSThread sleepForTimeInterval:10];
if ([[NSThread currentThread] isCancelled] == NO){
// 做一个改进,在需要执行的代码中,多加一层判断。
NSLog(@"Thread Loop");
}
}
NSLog(@"Thread Finished");
}
}


分析2:

这个用法的原理,其实跟NSOperation的isCancelled的用法是一个道理。

五、线程的同步/锁

后续补上。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程 ios