概述
---html
NSThread类是一个继承于NSObjct类的轻量级类。一个NSThread对象就表明一个线程。它须要管理线程的生命周期、同步、加锁等问题,所以会产生必定的性能开销。
使用NSThread类能够在特定的线程中被调用某个OC方法。当须要执行一个冗长的任务,而且不想让这个任务阻塞应用中的其余部分,尤为为了不阻塞app的主线程(由于主线程用于处理用户界面展现交互和事件相关的操做),这个时候很是适合使用多线程。线程也能够将一个庞大的任务分为几个较小的任务,从而提升多核计算机的性能。objective-c
NSThread类在运行期监听一个线程的语义和NSOperation类是类似的。好比取消一个线程或者决定一个任务执行完后这个线程是否存在。数组
本文将会从这几个方面开始探讨NSThread安全
方法属性的介绍
---多线程
初始化(建立)一个NSThread对象app
// 返回一个初始化的NSThread对象 - (instancetype)init // 返回一个带有多个参数的初始化的NSThread对象 // selector :线程执行的方法,最多只能接收一个参数 // target :selector消息发送的对象 // argument : 传给selector的惟一参数,也能够是nil - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument ); // iOS 10 - (instancetype)initWithBlock:(void (^)(void))block;
启动一个线程。异步
// 开辟一个新的线程,而且使用特殊的选择器Selector做为线程入口,调用完毕后,会立刻建立并开启新线程 + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument; // iOS 10 + (void)detachNewThreadWithBlock:(void (^)(void))block; // 启动接受者 - (void)start; // 线程体方法,线程主要入口,start 后执行 // 该方法默认实现了目标(target)和选择器(selector),用于初始化接受者和调用指定目标(target)的方法。若是子类化NSThread,须要重写这个方法而且用它来实现这个线程主体。在这种状况下,是不须要调用super方法的。 // 不该该直接调用这个方法。你应该经过调用启动方法开启一个线程。 - (void)main;
使用initWithTarget:selector:
、initWithBlock:
、detachNewThreadSelector:
,detachNewThreadWithBlock:
建立线程都是异步线程。oop
中止一个线程性能
// 阻塞当前线程,直到特定的时间。 + (void)sleepUntilDate:(NSDate *)date; // 让线程处于休眠状态,直到通过给定的时间间隔 + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 终止当前线程 + (void)exit; // 改变接收者的取消状态,来表示它应该终止 - (void)cancel;
决定线程状态.net
// 接收者是否存在 @property (readonly, getter=isExecuting) BOOL executing; // 接收者是否结束执行 @property (readonly, getter=isFinished) BOOL finished; // 接收者是否取消 @property (readonly, getter=isCancelled) BOOL cancelled;
主线程相关
// 当前线程是不是主线程 @property (class, readonly) BOOL isMainThread; // 接受者是不是主线程 @property (readonly) BOOL isMainThread; // 获取主线程的对象 @property (class, readonly, strong) NSThread *mainThread;
执行环境
// 这个app是不是多线程 + (BOOL)isMultiThreaded; // 返回当前执行线程的线程对象。 @property (class, readonly, strong) NSThread *currentThread; // 返回一个数组,包括回调堆栈返回的地址 @property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses ; // 返回一个数组,包括回调堆栈信号 @property (class, readonly, copy) NSArray<NSString *> *callStackSymbols;
线程属性相关
// 线程对象的字典 @property (readonly, retain) NSMutableDictionary *threadDictionary; NSAssertionHandlerKey // 接收者的名字 @property (nullable, copy) NSString *name; // 接收者的对象大小,以byte为单位 @property NSUInteger stackSize;
线程优先级
// 线程开启后是个只读属性 @property NSQualityOfService qualityOfService; // 返回当前线程的优先级 + (double)threadPriority; // 接受者的优先级,已经废弃,使用qualityOfService代替 @property double threadPriority; // 设置当前线程的优先级。设置线程的优先级(0.0 - 1.0,1.0最高级) + (BOOL)setThreadPriority:(double)p;
通知
// 未被实现,没有实际意义,保留项 NSDidBecomeSingleThreadedNotification // 在线程退出前,一个NSThread对象收到到退出消息时会发送这个通知。 NSThreadWillExitNotification // 当第一个线程启动时会发送这个通知。这个通知最多发送一次。当NSThread第一次发送用`detachNewThreadSelector:toTarget:withObject:`,`detachNewThreadWithBlock:`,`start`消息时,发送通知。后续调用这些方法是不会发送通知。 NSWillBecomeMultiThreadedNotification
线程间通讯,
在NSObject的分类NSThreadPerformAdditions中的方法(NSThread.h文件中)具备这些特性:
@interface NSObject (NSThreadPerformAdditions) // 若是设置wait为YES: 等待当前线程执行完之后,主线程才会执行aSelector方法; // 若是设置wait为NO:不等待当前线程执行完,就在主线程上执行aSelector方法。 // 若是,当前线程就是主线程,那么aSelector方法会立刻执行,wait是YES参数无效。 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array; // 等于第一个方法中modes是kCFRunLoopCommonModes的状况。指定了线程中 Runloop 的 Modes = kCFRunLoopCommonModes。 - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait; // 在指定线程上操做,由于子线程默认未添加NSRunloop,在线程未添加runloop时,是不会调用选择器中的方法的。 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:( NSArray<NSString *> *)array ; // 等于第一个方法中modes是kCFRunLoopCommonModes的状况。 - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait ; // 隐式建立子线程,在后台建立。而且是个同步线程。 - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg ; @end
// 当前线程操做。 - (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
NSRunLoop.h文件中
// 延迟操做 /**************** Delayed perform ******************/ @interface NSObject (NSDelayedPerforming) // 异步方法,不会阻塞当前线程,只能在主线程中执行。是把`Selector`加到主队列里,当 `delay`以后执行`Selector`。若是主线程在执行业务,那只能等到执行完全部业务以后才会去执行`Selector`,就算`delay`等于 0。 // 那`delay `从何时开始计算呢?从发送`performSelector`消息的时候。就算这时主线程在阻塞也会计算时间,当阻塞结束以后,若是到了`delay`那就执行`Selector`,若是没到就继续 `delay`。 // 只能在主线程中执行,在子线程中不会调到aSelector方法 - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes; // 等于第一个方法中modes是kCFRunLoopCommonModes的状况。指定了线程中 Runloop 的 Modes = kCFRunLoopCommonModes。 - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay; // 在方法未到执行时间以前,取消方法。调用这2个方法当前target执行dealloc以前,以确保不会Crash。 + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument; + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget; @end // 按照排序顺序执行 @interface NSRunLoop (NSOrderedPerform) // 按某种顺序order执行方法。参数order越小,优先级越高,执行越早 // selector都是target的方法,argument都是target的参数 // 这2个方法会设置一个定时器去在下个runloop循环的开始时让target执行aSelector消息。 定时器根据modes确认模式。当定时器触发,定时器尝试队列从runloop中拿出消息并执行。 若是run loop 正在运行,而且是指定modes的一种,则是成功的,不然定时器一直等待直到runloop是modes 中的一种。 - (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSRunLoopMode> *)modes; - (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg; - (void)cancelPerformSelectorsWithTarget:(id)target; @end
本文介绍大部分的知识点如思惟导图:
//1. 手动开启,action-target 方式 NSThread * actionTargetThread = [[NSThread alloc] initWithTarget:self selector:@selector(add:) object:nil]; [actionTargetThread start]; //2. 手动开启, block 方式 NSThread *blockThread = [[NSThread alloc] initWithBlock:^{ NSLog(@"%s",__func__); }]; [blockThread start]; //3. 建立就启动, action-target 方式 [NSThread detachNewThreadSelector:@selector(add2:) toTarget:self withObject:@"detachNewThreadSelector"]; //4. 建立就启动, block 方式 [NSThread detachNewThreadWithBlock:^{ NSLog(@"%s",__func__); }];
2.1 NSThreadPerformAdditions分类方法,异步调用方法
// 不管在子线程仍是主线程,都会调用主线程方法。
a. 主线程
[self performSelectorOnMainThread:@selector(add:) withObject:nil waitUntilDone:YES]; //[self performSelectorOnMainThread:@selector(add:) withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];
子线程默认没有开启runloop。须要手动添加,否则选择器方法没法调用。
b. 子线程
使用initWithBlock:
方式建立。
//1. 开辟一个子线程 NSThread *subThread1 = [[NSThread alloc] initWithBlock:^{ // 2.子线程方法中添加runloop // 3.实现线程方法 [[NSRunLoop currentRunLoop] run]; }]; //1.2. 启动一个子线程 [subThread1 start]; // 2. 在子线程中调用方法 // [self performSelector:@selector(add:) onThread:subThread1 withObject:@"22" waitUntilDone:YES]; [self performSelector:@selector(add:) onThread:subThread1 withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]];
使用initWithTarget:selector:object:
建立。
// 1. 开辟一个子线程 NSThread *subThread2 = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil]; // 1.2 启动一个子线程 [subThread2 start]; // 3. 在子线程中调用方法 // [self performSelector:@selector(add:) onThread:subThread2 withObject:@"22" waitUntilDone:YES]; [self performSelector:@selector(add:) onThread:subThread1 withObject:@"arg" waitUntilDone:YES modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]]; // 2.子线程方法中添加runloop - (void)startThread{ [[NSRunLoop currentRunLoop] run]; }
c. 后台线程(隐式建立一个线程)
[self performSelectorInBackground:@selector(add:) withObject:@"arg"];
2.2 协议NSObject方法
建立是的同步任务。
[NSThread detachNewThreadWithBlock:^{ // 直接调用 [self performSelector:@selector(add:) withObject:@"xxx"]; }];
2.3 延迟
NSObject分类NSDelayedPerforming方法,添加异步任务,而且是在主线程上执行。
[self performSelector:@selector(add:) withObject:self afterDelay:2];
2.4 按照顺序操做
NSRunLoop分类NSOrderedPerform中的方法
[NSThread detachNewThreadWithBlock:^{ NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop]; // 记得添加端口。否则没法调用selector方法 [currentRunloop addPort:[NSPort port] forMode:(NSRunLoopMode)kCFRunLoopCommonModes]; [currentRunloop performSelector:@selector(add:) target:self argument:@"arg1" order:1 modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]]; [currentRunloop performSelector:@selector(add:) target:self argument:@"arg3" order:3 modes:@[(NSRunLoopMode)kCFRunLoopDefaultMode]]; [currentRunloop run]; }];
问题:
多个线程可能会同时访问同一块资源。好比多个线程同时访问同一个对象、同一个变量、同一个文件等。当多个线程同时抢夺同一个资源,会引发线程不安全性,可能会形成数据错乱和数据安全问题。
解决:
使用线程同步技术: 能够对可能会被抢夺的资源,在被被竞争的时候加锁。让其保证线程同步状态。而锁具备多种类型:好比读写锁、自旋锁、互斥锁、信号量、条件锁等。在NSThread可能形成资源抢夺状况下,能够使用互斥锁。互斥锁就是多个线程任务按顺序的执行。
以下就使用的状况之一:对须要读写操做的资源,进行加锁操做。
for (NSInteger index = 0 ; index < 100; index ++) { @synchronized (self) { self.allCount -= 5; NSLog(@"%@卖出了车票,还剩%ld",[NSThread currentThread].name,self.allCount); } }
线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡。当线程启动后,它不能一直“霸占”着CPU独自运行,因此CPU须要在多条线程之间切换,因而线程状态也就会随之改变。
新建和就绪状态
显式建立,使用initWithTarget:selector:
和initWithBlock:
建立一个线程,未启动,只有发送start消息才会启动,而后处于就行状态。
使用detachNewThreadWithBlock:
和detachNewThreadSelector:toTarget:
显示建立并当即启动。 还有种建立方式,隐式建立并当即启动:performSelectorInBackground:withObject:
。
运行和阻塞状态
若是处于就绪状态的线程得到了CPU资源,开始执行可执行方法的线程执行体(block或者@Selector),则该线程处于运行状态。
当发生以下状况下,线程将会进入阻塞状态:
sleepUntilDate:
sleepForTimeInterval:
主动放弃所占用的处理器资源。// 1. 建立:New状态 NSThread * actionTargetThread = [[NSThread alloc] initWithTarget:self selector:@selector(add:) object:nil]; // 2. 启动:就绪状态 [actionTargetThread start]; // 可执行方法 - (void)add:(id)info{ // 3. 执行状态 NSLog(@"%s,info %@",__func__,info); // 5. 当前线程休眠 [NSThread sleepForTimeInterval:1.0]; NSLog(@"after"); // 4. 程序正常退出 } // 6. 打取消标签 [actionTargetThread cancel]; // 7. 主动退出 [NSThread exit];
注意: