iOS多线程编程--NSThread

Posted 乌戈勒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS多线程编程--NSThread相关的知识,希望对你有一定的参考价值。

前言:

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多线程编程--NSThread的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程1-NSThread

iOS多线程编程之NSThread的使用

iOS多线程编程之NSThread的使用

iOS多线程编程--NSThread

iOS开发开辟线程总结--NSThread

16iOS多线程篇:NSThread