iOS多线程NSThread的使用
1、创建线程
detach方法:直接创建并启动一个线程,由于没有返回值,如果需要获取新创建的线程,需要在执行的selector方法中调用-[NSThread currentThread]获取。
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
init方法:初始化线程,并返回NSThread对象,线程创建出来之后需要手动调用-start方法启动。
– (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument;
– (instancetype)initWithBlock:(void (^)(void))block;
2、线程操作
创建好线程之后需要对线程进行操作,NSThread给线程提供的主要操作方法有启动、睡眠、取消、退出
1、启动
我们使用init方法将线程创建出来之后, 线程并不会立即运行, 只有我们手动调用-start方法之后才会启动线程(detachNewThread创建的线程会自动启动)。
– (void)start;
注意: 部分线程属性需要在启动前设置 , 线程启动之后再设置会无效. 如qualityOfService属性。
2、睡眠
NSThread提供了2种让线程睡眠的方法,一个是根据NSDate传入睡眠时间,一个是直接传入NSTimeInterval(睡眠时间)。
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
这两个方法作用一样,相当于执行一个sleep的任务。在执行过程中,即使有其他任务传入runloop,runloop也不会立即响应,必须sleep任务完成之后,才会响应其他任务。
调用睡眠方法后,线程会立即让出当前时间片,让出CPU资源,进入阻塞状态。
3、取消
对于线程的取消,NSThread提供了一个取消的方法和一个属性。
– (void)cancel;
@property (readonly, getter=isCancelled) BOOL cancelled;
调用-cancel方法并不会立刻取消线程,它仅仅是将cancelled属性设置为YES。cancelled也仅仅是一个用于记录状态的属性。线程取消的功能需要我们在main函数中自己实现。
要实现取消的功能,我们需要自己在线程的main函数中定期检查isCancelled状态来判断线程是否需要退出,当isCancelled为YES的时候,我们手动退出。如果我们没有在main函数中检查isCancelled状态,那么调用cancel将没有任何意义。
– (void)onThread {
for (int i = 0; i < 5; i++) {
//睡眠1秒
[NSThread sleepForTimeInterval:1];
//判断是否已经取消
if (NSThread.currentThread.isCancelled) {
return;
}
}
}
4、退出
与充满不确定性的-cancel相比,+exit函数可以让线程立即退出。
+ (void)exit;
+exit 调用之后会立即终止线程,即使任务还没有执行完成也会中断,这就非常有可能导致内存泄漏等严重问题,所以一般不推荐使用。
– (void)onThread {
for (int i = 0; i < 5; i++) {
//睡眠1秒
[NSThread sleepForTimeInterval:1];
if (i == 3) {
[NSThread exit];
}
}
}
对于有runloop的线程,可以使用CFRunLoopStop()结束runloop配合-cancel结束线程
– (void)onThread {
// 给线程设置名字
[[NSThread currentThread] setName:@”myThread”];
// 给线程添加runloop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 给runloop添加数据源
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// 检查isCancelled
While(![[NSThread currentThread] isCancelled]) {
// 启动runloop
[runloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10];
}
}
5、线程状态
//判断线程是否正在执行
@property (readonly, getter=isExecuting) BOOL executing;
//判断线程是否结束
@property (readonly, getter=isFinished) BOOL finished;
//判断线程是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled;
3、线程通讯
线程准备好之后,经常需要从主线程把耗时的任务丢给辅助线程,当任务完成之后辅助线程再把结果传回主线程,这些线程通讯一般用的都是perform方法:
// 1、将selector丢给主线程执行, 可以指定runloop mode
– (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
// 2、将selector丢给主线程执行, runloop mode默认为common mode
– (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// 3、创建一个后台线程执行selector
– (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
// 4、将selector丢给指定线程执行, 可以指定runloop mode
– (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AV;
// 5、将selector丢给指定线程执行, runloop mode默认为default mode
– (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
所以我们一般用3、4、5方法将任务丢给辅助线程,任务执行完成之后再使用1、2方法将结果传回主线程;
注意: perform方法只对拥有runloop的线程有效,如果创建的线程没有添加runloop,perform的selector将无法执行;
4、线程优先级
NSThread有4个优先级的API:
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
@property double threadPriority;
@property NSQualityOfService qualityOfService
前两个是类方法, 用于设置和获取当前线程的优先级。
threadPriority属性可以通过对象来设置和获取优先级。
由于线程优先级是一个比较抽象的东西,没人知道0.5和0.6到底有多大区别,所以iOS8之后新增了qualityOfService枚举属性,大家可以通过枚举值设置优先级。
typedef NS_ENUM(NSInteger, NSQualityOfService) {
// *高优先级, 主要用于直接参与提供交互式UI的工作, 例如: 处理点击事件或绘制图像到屏幕上
NSQualityOfServiceUserInteractive = 0x21,
// 次高优先级, 用于执行用户明确请求的工作, 并且必须立即显示结果, 以便进行进一步的用户交互
NSQualityOfServiceUserInitiated = 0x19,
// 默认优先级, 当没有设置优先级的时候, 线程默认优先级
NSQualityOfServiceUtility = 0x11,
// 普通优先级,主要用于不需要立即返回的任务
NSQualityOfServiceBackground = 0x09,
// 后台优先级, 用于完全不紧急的任务
NSQualityOfServiceDefault = -1
};
一般主线程和没有设置优先级的线程都是默认优先级。
5、主线程和当前线程
NSThread提供了非常方便的获取和判断主线程的API:
// 判断当前线程是否是主线程
@property (readonly) BOOL isMainThread;
@property (class, readonly) BOOL isMainThread;
// 获取主线程的thread
@property (class, readonly, strong) NSThread *mainThread;
// 获取当前线程
@property (class, readonly, strong) NSThread *currentThread;
6、线程通知
NSThread有三个线程相关的通知:
// 由当前线程派生出*个其他线程时发送, 一般一个线程只发送一次
FOUNDATION_EXPORT NSNotificationName const NSWillBecomeMultiThreadedNotification;
// 这个通知目前没有实际意义, 可以忽略
FOUNDATION_EXPORT NSNotificationName const NSDidBecomeSingleThreadedNotification;
// 线程退出之前发送这个通知
FOUNDATION_EXPORT NSNotificationName const NSThreadWillExitNotification;
7、解决线程冲突
同步代码块
使用@synchronized包围的代码即为同步代码块,它可以防止不同的线程同时执行同一段代码。
– (void)onThread {
@synchronized (self) {
self.money += 10;
[NSThread sleepForTimeInterval:0.1];
}
}
锁机制
创建一个NSLock类的锁对象,lock方法用于加锁,如果锁被其他对象占用则线程被阻塞,unlock方法用于解锁,以便其他线程加锁。
self.lock = [[NSLock alloc] init];
– (void)onThread {
[self.lock lock];
[NSThread sleepForTimeInterval:0.1];
self.money -= 10;
[self.lock unlock];
}
线程间通信
线程的调度对于开发者来说是透明的,我们不能也无法预测线程执行的顺序,但有时我们需要线程按照一定条件来执行,这时就需要线程间进行通信,NSCondition提供了线程间通信的方法。
@interface NSCondition : NSObject <NSLocking> {
@private
void *_priv;
}
/*
调用NSCondition对象wait方法的线程会阻塞,直到其他线程调用该对象的signal方法或broadcast方法来唤醒
唤醒后该线程从阻塞态改为就绪态,交由系统进行线程调度
执行wait方法时内部会自动执行unlock方法释放锁,并阻塞线程
*/
– (void)wait;
//同上,只是该方法是在limit到达时唤醒线程
– (BOOL)waitUntilDate:(NSDate *)limit;
/*
唤醒在当前NSCondition对象上阻塞的一个线程
如果在该对象上wait的有多个线程则随机挑选一个,被挑选的线程则从阻塞态进入就绪态
*/
– (void)signal;
/*
同上,该方法会唤醒在当前NSCondition对象上阻塞的所有线程
*/
– (void)broadcast;
@property (nullable, copy) NSString *name;
@end
NSCondition实现了NSLocking协议,所以NSCondition同样具有锁的功能,与NSLock一样可以加锁与解锁的操作。
self.condition = [[NSCondition alloc] init];
NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(onThread1) object:nil];
[thread1 start];
NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(onThread2) object:nil];
[thread2 start];
– (void)onThread1 {
while (true)
{
//首先使用condition上锁,如果其他线程已经上锁则阻塞
[self.condition lock];
if (self.haveMoney)
{
self.haveMoney = NO;
//唤醒其他在condition上等待的线程
[self.condition broadcast];
}
else
{
//如果没钱则等待,并阻塞
[self.condition wait];
//如果阻塞的线程被唤醒后会继续执行代码
}
//释放锁
[self.condition unlock];
}
}
– (void)onThread2 {
while (true)
{
//首先使用condition上锁,如果其他线程已经上锁则阻塞
[self.condition lock];
if (!self.haveMoney)
{
self.haveMoney = YES;
//唤醒其他在condition上等待的线程
[self.condition broadcast];
}
else
{
//如果有钱则等待,并阻塞
[self.condition wait];
//如果阻塞的线程被唤醒后会继续执行代码
}
//释放锁
[self.condition unlock];
}
}