内容简介:NSThread 类是一个继承 NSObjct 类的个轻量级的类。但需要管理线程的生命周期、同步、加锁等问题,这会导致一定的性能开销。 使用 NSThread 类可以让某个 OC 方法在特定的线程中被调用。当需要执行一个冗长的任务,并且不想让这个任务阻塞应用中的其他部分,尤其为了避免不阻塞 app 的主线程(因为主线程用于处理用户界面展示交互和事件相关的操作),这个时候非常适合使用多线程。线程也可以将一个庞大的工作分为几个较小的工作,从而提高多核计算机的性能。NSThread 类支持监听一个线程在运行期情
NSThread 类是一个继承 NSObjct 类的个轻量级的类。但需要管理线程的生命周期、同步、加锁等问题,这会导致一定的性能开销。 使用 NSThread 类可以让某个 OC 方法在特定的线程中被调用。
当需要执行一个冗长的任务,并且不想让这个任务阻塞应用中的其他部分,尤其为了避免不阻塞 app 的主线程(因为主线程用于处理用户界面展示交互和事件相关的操作),这个时候非常适合使用多线程。线程也可以将一个庞大的工作分为几个较小的工作,从而提高多核计算机的性能。
NSThread 类支持监听一个线程在运行期情况的语义和 NSOperation 相似。比如取消一个线程或者决定一个任务执行完后这个线程是否存在。 一个 NSThread对象就代表一个线程。
本文将会从这几个方面开始探讨 NSThread
方法属性的介绍
初始化(创建)一个 NSThread 对象
// 返回一个初始化的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 ; 复制代码
启动一个线程。 使用 initWithTarget:selector:
、 initWithBlock:
、 detachNewThreadSelector:
, detachNewThreadWithBlock:
创建都是异步线程。
// 开辟一个新的线程,并且使用特殊的选择器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 复制代码
停止一个线程
// 阻塞当前线程,直到特定的时间。 + (void)sleepUntilDate:(NSDate *)date; // 让线程处于休眠状态,在给定的时间间隔 + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 终止当前线程 + (void)exit; // 改变接收者的取消状态,来表示它应该终止 - (void)cancel 复制代码
决定线程状态
// 接收者是否存在 @property (readonly, getter=isExecuting) BOOL executing; // 接收者是否结束执行 @property (readonly, getter=isFinished) BOOL finished; // 接收者是否取消 @property (readonly, getter=isCancelled) BOOL cancelled; 复制代码
主线程相关
// 返回Boolean值,表示当前线程是否是主线程 @property (class, readonly) BOOL isMainThread ; // Boolean值,表示接受者是否是主线程 @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:`,`start`方法。后续调用这些方法是不会发送通知。这个通知的观察者有他们的通知方法是在主线程中调用而新线程。观察者通知总是被执行,当新的线程开始执行。 NSWillBecomeMultiThreadedNotification 复制代码
线程间通信, NSObject分类NSThreadPerformAdditions中方法(NSThread.h文件中) 1、在主线程和子线程中均可执行,均会调用主线程的aSelector方法; 2、方法是异步的
@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 复制代码
直接给接受者发消息的其他方法。
- 协议NSObject中的方法,可在主线程或者子线程执行,在当前线程执行的同步任务,会阻塞当前线程。等同于直接调用方法。
// 当前线程操作。 - (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 复制代码
本文介绍大部分的知识点如思维导图:
使用
- 创建线程 用initXXX初始化的需要调用start方法来启动线程。而detachXXX初始化方法,直接启动线程。显示调用线程。
//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. 子线程
//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]]; 复制代码
// 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]; }]; 复制代码
线程安全
问题: 多个线程可能会访问同一块资源。比如多个线程同时访问同一个对象、同一个变量、同一个文件等。多个线程同时抢夺同一个资源,会引起线程不安全,可能会造成数据错乱和数据安全问题。 解决: 使用线程同步技术:在可能会被抢夺的资源,在被被竞争的时候加锁。让其保证线程同步状态。锁有多种:读写锁、自旋锁、互斥锁、信号量、条件锁等。互斥锁就是多个顺序的执行。这里用互斥锁的情况。
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),则该线程处于运行状态。
当发生如下情况下,线程将会进入阻塞状态:
- 线程调用 sleep 方法:
sleepUntilDate:
sleepForTimeInterval:
主动放弃所占用的处理器资源。 - 线程调用了一个阻塞式 IO 方法,在该方法返回之前,该线程被阻塞。 线程试图获得一个同步监视器,但该同步监视器正被其他线程锁持有。
- 线程在等待某个通知(notify)。
- 程序调用了线程的suspend方法将该线程挂起。不过这个方法容易导致死锁,所以程序应该尽量避免使用该方法。 当前正在执行的线程被阻塞之后,其他线程就可以获得执行的机会了。被阻塞的线程会在合适时候重新进入就绪状态,注意是就绪状态而不是运行状态。也就是 说被阻塞线程的阻塞解除后,必须重新等待线程调度器再次调度它。 针对上面的几种情况,当发生如下特定的情况将可以解除上面的阻塞,让该线程重新进入就绪状态:
- 调用sleep方法的线程经过了指定时间。
- 线程调用的阻塞式 IO 方法已经返回。
- 线程成功地获得了试图取得同步监视器。
- 线程正在等待某个通知时,其他线程发出了一个通知。
- 处于挂起状态的线程被调用了resume恢复方法。
- 线程死亡
- 可执行方法执行完成,线程正常结束。
- 程序的意外奔溃。
- 该线程的发送exit消息来结束该线程。
// 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]; 复制代码
总结:
- NSThread 管理多个线程比较困难,所以不太推荐使用
- 苹果建议,现在推荐用 GCD 和 NSOperation
- [NSTread currentThread] 跟踪任务所在线程,适用于 NSTread,NSOperation,和 GCD
- 用 NSThread 创建的线程,不会自动添加 autoreleasepool
参考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Numerical Linear Algebra
Lloyd N. Trefethen、David Bau III / SIAM: Society for Industrial and Applied Mathematics / 1997-06-01 / USD 61.00
Numerical Linear Algebra is a concise, insightful, and elegant introduction to the field of numerical linear algebra.一起来看看 《Numerical Linear Algebra》 这本书的介绍吧!