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

iOS多线程之NSThread

2016-03-18 12:01 501 查看
前言

多线程的价值无需赘述,对一个app性能和用户交互起着至关重要的作用。说道多线程,不得不提一个老猿常谈的问题:没错!什么是进程?什么是线程?两者有什么区别。我的天,瞬间脑洞又大了!赶紧翻操作系统!

科普:进程与线程

进程是程序在数据集上的执行过程。注意进程不是程序,而是程序执行的一个动态的过程。早期的多道程序系统计算机是没有线程这个概念的,那时候的进程是程序在操作系统中的最小执行单位,同时也是资源分配和调度的最小单元。但是由于进程的调度十分浪费时间,进程的创建和撤销伴随着大量资源的分配和释放,CPU在这期间一直在做无用功,严重影响了并发的效率。为了提高程序并发执行的效率,线程诞生了!一个进程中,程序可以在多条执行路径上执行,每一条执行路径对应一个线程,因此线程就成了程序执行的最小单元。但是进程依旧是资源调度和分配的最小单元,各个线程共享所在进程中的资源,同时维护自己的堆栈和少量资源。线程在创建和过程中并不会伴随大量资源的分配和释放,这就大大提高了并发的效率!

因此对于那个史前巨问,可以如下回答:

进程是操作系统资源分配和调度的最小单元,是为并发而生的

线程是程序执行的最小单元,是为提高并发效率而生的

多线程则是指一个进程包含多个线程

PS你要是问我什么是并发?温馨提示!还是老老实实把操作系统这块内容再看一遍吧!

iOS多线程框架

苹果对于多线程机制提供了3套框架:NSThread、GCD(Grand Central Dispatch)、NSOperation。另外还有跨平台的多线程框架pthread(POSIX threads),是用C写的,想必在iOS中应该没人用吧!

NSThread是一套OC框架,偏底层,封装性较差,主要基于thread(线程)使用。NSThread需要手动管理线程的生命周期,包括创建、开启、睡眠、取消、终止。因此NSThread在灵活性高的同时,对于编程者确是不那么友好。

GCD是一套基于C语言的框架,无需手动管理生命,基于task(任务)使用,比较方便,是官方推荐的多线程用法。

NSOperation是OC的框架,封装了GCD,基于queue(队列)使用,对于较为复杂的多线程项目比较适合。

NSThread API

一个NSThread对象即对应一个线程,由于需要手动管理线程的生命周期,因此对NSThread主要介绍线程的相关操作。包括创建、开启、睡眠、取消(并非生命周期一部分)、终止。

线程操作

创建

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)ar;


类方法直接创建一个线程并开启执行,该方法不返回线程对象,可以在selector中获取当前线程。

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);


实例方法,实例化一个线程对象并返回,并将selector和target传入main函数(入口函数)。这个方法只是用来创建线程,需要手动start(开启)。

另外,创建线程是有开销的,iOS下主要成本包括构造内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,不过可以使用方法-setStackSize:自己设置,注意必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间

开启

- (void)start NS_AVAILABLE(10_5, 2_0);


开启线程实例,在新线程中执行入口函数。若开启的线程是当前应用第一个非主线程,NSWillBecomeMultiThreadedNotification通知会被发送给通知中心。

睡眠

睡眠即阻塞,用于阻塞当前线程,阻塞期间不会执行任何任务。系统提供了两种睡眠的类方法

+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;


阻塞到指定时刻

阻塞指定的时间间隔

取消

@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);

- (void)cancel NS_AVAILABLE(10_5, 2_0);


对于线程的取消,系统提供了相应的属性和一个实例方法。值得注意的是,这个取消并不是退出线程,之前介绍的时候特意标注“取消”并不是线程生命周期的一部分!官方文档对cencel的解释是:

Changes the cancelled state of the receiver to indicate that it should exit.

因此,不要指望调用cancel会退出当前线程(这是exit要做的事)。对一个线程调用cancel,只是改变了线程当前的执行状态,即告诉指定线程应该要退出了。我们要做的就是在线程main方法中周期性地监听线程的状态,一旦isCancelled返回YES,就终止线程。注意这个main方法不是application的main方法,是线程内部的main方法,关于main方法将在下面继续介绍。

PS关于监听状态涉及到RunLoop,由于本人才疏学浅,还没开始研究RunLoop,所以先设个//TODO: RunLoop

终止

+ (void)exit;


类方法,终止当前线程。在线程被终止前会发送NSThreadWillExitNotification通知给通知中心。由于通知是同步发送的,因此可以确保在线程终止前,通知中心已经收到通知了。应该慎重调用这个方法,因为它无法保证资源的释放,造成内存泄露。

Invoking this method should be avoided as it does not give your thread a chance to clean up any resources it allocated during its execution.

当线程撤销后进程中只剩下主线程,则会在撤销之前发送NSDidBecomeSingleThreadedNotification通知给通知中心。

其他API

线程优先级

实际需求中,每个线程的紧急程度可能不同,有的线程需要优先执行。系统给出了设置线程优先级的接口,需要注意的是线程一旦开启再设置优先级就无效了。系统提供了4个线程优先级相关的接口:

+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;

@property double threadPriority NS_AVAILABLE(10_6, 4_0); // To be deprecated; use qualityOfService below

@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0); // read-only after the thread is started


threadPriority属性取值为0.0~1.0的double,1.0优先级越高。然而事实上线程优先级是由OS内核决定的,谁也不知道这个属性值映射到操作系统会被处理成什么样的优先级。

Note:because the priority is determined by the kernel, there is no guarantee what this value actually will be.

为此用threadPriority描述优先级是一个比较抽象的概念,iOS8引入qualityOfService来描述线程优先级,表现更为友好。threadPriority是一个NSQualityOfService枚举变量,取值如下:

typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,  //最高优先级
NSQualityOfServiceUserInitiated = 0x19, //次高优先级
NSQualityOfServiceDefault = -1              //默认优先级
NSQualityOfServiceUtility = 0x11,           //次低优先级
NSQualityOfServiceBackground = 0x09,        //最低优先级
} NS_ENUM_AVAILABLE(10_10, 8_0);


主线程和没有设置优先级的线程默认都是NSQualityOfServiceDefault优先级。

线程通信

各个线程之间通常需要进行交互,例如在子线程中加载数据之后需要在主线程更新UI。API中提供了NSObject支持线程通信的Category:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array; //①

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait; //②

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array    //③

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait //④

- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg  //⑤


方法①和方法②提供了子线程到主线程的单向通信

方法③和方法④提供了当前线程到任意线程之间的通信,比较灵活

方法⑤其实是创建了一个子线程来接受selecter

其他

+ (NSThread *)currentThread;


获取当前线程实例

+ (NSThread *)mainThread


获取主线程

@property (readonly) BOOL isMainThread
+ (BOOL)isMainThread


判断是否是主线程

+ (BOOL)isMultiThreaded;


判断当先进程是否为多线程环境

@property (nullable, copy) NSString *name


线程名属性

@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0)


线程状态属性,只读,判断线程是否处于执行状态、取消状态还是已经终止。

- (void)main NS_AVAILABLE(10_5, 2_0);   // thread body method


这个一个比较重要方法,先来看一下官方的Discussion:

The default implementation of this method takes the target and selector used to initialize the receiver and invokes the selector on the specified target. If you subclass NSThread, you can override this method and use it to implement the main body of your thread instead. If you do so, you do not need to invoke super.

You should never invoke this method directly. You should always start your thread by invoking the start method.

这个方法是线程的入口函数,当线程开启,默认会调用这个方法,并将线程入口函数selector和target传入,在该方法中对target调用selector。默认情况下,调用完毕,线程就被自动关闭了。

可以通过subclass NSThread and override this methods,实现线程开启后的自定义需求,例如之前所说的监听线程是否isCancelled就是在这个方法中配合RunLoop进行的,再监听到线程取消状态时再手动关闭线程。

总结

NSThread是iOS一个比较底层的处理多线程的框架,运用过程中以“线程”为操作重点,需要手动管理线程的生命周期,使用起来不太方便。下一节将介绍iOS提供的GCD框架,也是苹果推荐的处理多线程的方式,以”任务”为操作重点,且无需自行管理线程的生命周期。

本篇参考文档:

《NSThread Class Reference》

《小笨狼漫谈多线程:NSThread》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  ios 多线程 NSThread