多线程之NSThread
NSThread分析
初始化NSThread对象
- init
返回一个NSThread对象
- initWithTarget:selector:object:
返回一个根据给定参数初始化的NSThread对象
- initWithBlock:
返回一个NSThread对象,iOS 10 以后可用
- initWithTarget:selector:object:
会持有target
和object
,但不会像timer
一样无法释放,这里....
启动线程
+ detachNewThreadSelector:toTarget:withObject:
开辟一个子线程并执行select方法
+ detachNewThreadWithBlock:
开辟一个子线程并执行block
- start
开启线程
- main
线程的入口函数
+ detachNewThreadSelector:toTarget:withObject:
和+ detachNewThreadWithBlock:
会创建一个子线程,并调用start
方法,再执行完任务后自动调用exit
类方法(断点可验证),退出线程,在执行过程中会持有target
、object
和block
。
当非主线程的第一个线程创建时,NSTread
会发布一次此通知 --- 使用detachNewThreadSelector:toTarget:withObject:
或 start
方法,创建子线程。在新线程开始执行之前会发送该通知
start
方法会创建一个新的线程,并在新线程上调用NSThread
的mian
方法(子类可以重写该方法),main
方法中的默认实现为:
// - initWithTarget:selector:object:一一对应的关系,显而易见
[target performSelector:selector withObject:arg];
停止线程
+ sleepUntilDate:
直到给定的date之前,线程都是睡眠状态,此时runloop无法运行
+ sleepForTimeInterval:
直到给定的时间间隔interval之前,线程都是睡眠状态
+ exit
退出当前线程,再退出线程之前会发送一个NSThreadWillExitNotification通知
- cancel
更改thread的取消状态,表达该线程如果已经被取消,则退出
决定线程的执行状态属性
@property(readonly, getter=isExecuting) BOOL executing;
如果当前线程正在执行,则返回YES,否则为NO
@property(readonly, getter=isFinished) BOOL finished;
如果当前线程已经执行完毕,则返回YES,否则为NO
@property(readonly, getter=isCancelled) BOOL cancelled;
如果接收器已被取消,则为 YES,否则为 NO。
如果线程支持取消,则应定期检查此属性,如果返回 YES 则退出。
主线程相关属性
@property(class, readonly) BOOL isMainThread;
如果当前线程是主线程,则返回YES,否则为NO
@property(readonly) BOOL isMainThread;
如果消息接收者(线程)是主线程,则返回YES,否则为NO
@property(class, readonly, strong) NSThread *mainThread;
返回主线程对象
线程执行环境
+ 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
线程名字
@property NSUInteger stackSize
线程栈大小,必须是4kb的倍数。设置栈的大小,必须在线程启动之前才有效,在线程启动后设置的值并不会 真正 地生效
线程优先级
qualityOfService
在线程开启后,只读
+ threadPriority
返回当前线程的优先级,0.0 ~ 1.0, 1.0为最高级。优先级有内核决定,具体值待定
+ setThreadPriority:
设置线程的优先级,0.0 ~ 1.0, 1.0为最高级。设置成功返回YES
NSThread相关API
下列方法都来自NSObject
的分类NSObject (NSThreadPerformAdditions)
主线程执行
- performSelectorOnMainThread:withObject:waitUntilDone:modes:
- performSelectorOnMainThread:withObject:waitUntilDone:
这两个方法都在主线程中执行,可以从子线程回到主线程中。waitUntilDone
,YES
表示阻塞线程,需要等待selector
中代码运行完再往下执行;NO
的话表示,不必等待selector
中的代码执行完毕,直接往下执行。
waitUntilDone
为NO
时,表示不需要阻塞线程,需要依托于RunLoop
的- performSelector:target:argument:order:modes:
实现
自定义线程执行
- performSelector:onThread:withObject:waitUntilDone:modes:
- performSelector:onThread:withObject:waitUntilDone:
1.当前线程与执行线程相同
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
[self performSelector:@selector(log) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];
NSLog(@"3");
});
- (void)log {
NSLog(@"2");
}
打印结果 1 2 3。这里的waitUntilDone
为YES
,由上文可知,会阻塞线程,需要等待log
中的代码执行完毕才能继续往下执行。
接着,就该waitUntilDone
为NO
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
[self performSelector:@selector(log) onThread:[NSThread currentThread] withObject:nil waitUntilDone:NO];
NSLog(@"3");
});
- (void)log {
NSLog(@"2");
}
打印结果 1 3,此时,并不需要等待log
方法执行完毕,所以1和3先打印,另外由于waitUntilDone
为NO
,所以log
方法的执行就需要依靠当前线程的RunLoop
来执行,由于该GCD会开辟一个子线程执行任务(子线程中RunLoop默认不开启),所以log
并不会执行
如果想执行打印2的代码,需要进行线程保活操作,需要保证子线程中的RunLoop
开启并且不能阻塞。
2.当前线程与执行线程不同
// self.thread是一个常驻线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(currentState) onThread:self.thread withObject:nil waitUntilDone:YES];
});
查询了很多资料这里没有明确的说明,GNUStep中也没有到具体的实现。但是利用callStackSymbols
属性查看堆栈信息,可以发现走的是RunLoop
中的source0类型处理
tip: 从调用栈中发现
__CFRunLoopDoSource0
这个函数的存在,但是在Objective-C
版本的RunLoop
源码中并没有它的存在,于是就去找了Swift
版本的,果然有(项目是纯Objective-C
的)。
waitUntilDone
这里不管是YES
或者NO
,走的都是RunLoop
的souce0处理方式,和上面有些不同的是,waitUntilDone
为YES
的时候,系统加了条件锁(GNU实现方式)来实现阻塞线程的效果
后台线程
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
隐式创建一个子线程,并且调用start方法启动线程执行aSelector
,和+ detachNewThreadSelector:toTarget:withObject:
效果相同。
值得一提的是,这两个方法都是利用- initWithTarget:selector:object:
创建子线程,并调用start
NSThread应用
- (void)printWhat1 {
NSLog(@"1");
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"2");
}];
NSLog(@"3");
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
NSLog(@"5");
[thread start];
NSLog(@"6");
}
- (void)test {
NSLog(@"4");
}
打印结果:1 3。
原因分析: 执行到[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
这句代码后,程序卡死。这里和原文理解不同
造成这个结果有2个因素:
- 当前环境是主线程,但是
onThread:thread
中的thread
是子线程,这是两个不同的线程,selector
的执行会交给RunLoop
来执行 waitUntilDone
为YES
,会阻塞主线程
- performSelector:onThread:withObject:waitUntilDone
在这个因素同时满足时,会添加一个条件锁,再执行完selector
后,会进行解锁。又因为这是一个子线程,RunLoop
默认是不开启的,所以selector
无法执行,也就无法解锁,那么就卡死在那了
这和原文中的相互等待的理解有所不同
把waitUntilDone
改成NO
之后,不再满足上述条件,所以并不会添加条件锁,使得程序可以继续执行,这才是本质原因。同样的4
还是不会打印,也是因为子线程中的RunLoop
默认不开启,无法执行selector
。