NSTherad
简介
NSThread 是封装最小、最轻量级的多线程编程接口,它使用灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大
1、NSTherad创建
创建线程 手动启动线程
//创建线程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
//启动线程
[thread start];
创建线程并自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
隐式创建并启动线程
[self performSelectorInBackground:@selector(run) withObject:nil];
iOS10之后方法
NSThread *thread = [[NSThread alloc]initWithBlock:^{
//在此执行耗时操作
}];
[thread start];
///ios10之后可以使用 NSThreadWillExitNotification监听不到线程执行完毕
[NSThread detachNewThreadWithBlock:^{
//执行耗时操作
}];
2、NSTherad方法属性介绍
//获取当前线程
@property (class, readonly, strong) NSThread *currentThread;
//判断是否是多线程
+ (BOOL)isMultiThreaded;
//返回线程对象的字典
@property (readonly, retain) NSMutableDictionary *threadDictionary;
//阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
// 使当前线程睡眠指定的时间,单位为秒
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 退出当前线程
+ (void)exit;
//线程的调度优先级,取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高
+ (double)threadPriority;
设置优先级程度在iOS8.0之后,建议使用qualityOfService属性
@property double threadPriority
/*NSQualityOfService是个枚举,取值有如下优先级从高到低五种:
1.NSQualityOfServiceUserInteractive = 0x21 最高:主要用于与UI交互的操作,各种事件处理以及绘制图像等。
2.NSQualityOfServiceUserInitiated = 0x19 次高:执行一些明确需要立即返回结果的任务。例如,用户在邮件列表中选择邮件后加载电子邮件。
3.NSQualityOfServiceDefault = -1 默认:默认的优先级,介于次高级和普通级之间。
4.NSQualityOfServiceUtility = 0x11 普通:用于执行不许要立即返回结果、耗时的操作,下载或者一些媒体操作等。
5.NSQualityOfServiceBackground = 0x09 后台:后台执行一些用户不需要知道的操作,它将以最有效的方式运行。例如一些预处理的操作,备份或者同步数据等等。*/
@property NSQualityOfService qualityOfService
//线程的名字 用于区分线程
@property (nullable, copy) NSString *name
//接收器的堆栈大小,以字节为单位
@property NSUInteger stackSize
//是否为主线程
@property (readonly) BOOL isMainThread
//当前线程对象是否正在执行任务
@property (readonly, getter=isExecuting) BOOL executing
//当前线程对象是否已执行完任务
@property (readonly, getter=isFinished) BOOL finished
//当前线程对象是否被取消
@property (readonly, getter=isCancelled) BOOL cancelled
//取消当前线程
- (void)cancel
// 开启线程
- (void)start
//这个方法是线程的入口函数,当线程开启,默认会调用这个方法,并将线程入口函数selector和target传入,在该方法中对target调用selector。默认情况下,调用完毕,线程就被自动关闭了
- (void)main
/////这个通知只会被NSThread触发一次,条件是当第一个进程在调用了start或者detachNewThreadSelector:toTarget:withObject:方法.这个通知的接收者的一些处理方法都是在主线程上进行的;这是因为这个通知是在系统生产新的子线程之前进行的,所以在监听这个通知的时候,调用监听者的通知方法都会在主线程进行.
//FOUNDATION_EXPORT NSNotificationName const NSWillBecomeMultiThreadedNotification;
/////这个通知目前没有实际意义,苹果也仅仅是保留这个扩展通知而已,并没有在NSThread的方法中,来触发这个消息,不过根据这个通知的字面意思来理解,是进程又回到单一线程的时候,会发送这个通知;但在多线程环境下, 这个没有什么实际的处理工作,可暂时忽略;
//FOUNDATION_EXPORT NSNotificationName const NSDidBecomeSingleThreadedNotification;
/////在线程被终止前会发送NSThreadWillExitNotification通知给通知中心。由于通知是同步发送的,因此可以确保在线程终止前,通知中心已经收到通知了。应该慎重调用这个方法,因为它无法保证资源的释放,造成内存泄露。
//FOUNDATION_EXPORT NSNotificationName const NSThreadWillExitNotification;
/* 线程间通信的方法
* 多了一个modes参数,是用来设置runLoop模式
*/
- (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
常用场景
耗时操作
/**
* 下载图片,下载完之后回到主线程进行 UI 刷新
*/
- (void)downloadImage {
NSLog(@"current thread -- %@", [NSThread currentThread]);
// 1. 获取图片 imageUrl
NSURL *imageUrl = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1575461632017&di=78230c4f59a22f2b08269356ecbb1197&imgtype=0&src=http%3A%2F%2Fimg.sccnn.com%2Fbimg%2F326%2F203.jpg"];
// 2. 从 imageUrl 下载图片 耗时操作
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
// 通过二进制 data 创建 image
UIImage *image = [UIImage imageWithData:imageData];
// 3. 回到主线程进行图片赋值和界面刷新
[self performSelectorOnMainThread:@selector(refreshOnMainThread:) withObject:image waitUntilDone:YES];
}
/**
* 回到主线程进行图片赋值和界面刷新
*/
- (void)refreshOnMainThread:(UIImage *)image {
NSLog(@"current thread -- %@", [NSThread currentThread]);
// 赋值图片到imageview
self.imageView.image = image;
}
NSThread 线程安全
如果多个线程同时访问读取一块地址空间 数据会错乱
举例子 一个食堂 2个窗口同时卖馒头 馒头总共100个 卖完为止
不考虑线程安全的代码:
-(void)initmesshall
{
// 1. 设置馒头总共为 50个
self. bunCount = 50;
// 2. 设置食堂窗口1的线程
self. messhallwindow1 = [[NSThread alloc]initWithTarget:self selector:@selector(sellfood) object:nil];
self.messhallwindow1.name = @"食堂售饭窗口1";
// 3. 设置食堂窗口2的线程
self.messhallwindow2 = [[NSThread alloc]initWithTarget:self selector:@selector(sellfood) object:nil];
self.messhallwindow2.name = @"食堂售饭窗口2";
// 4. 开始卖馒头
[self.messhallwindow1 start];
[self.messhallwindow2 start];
}
/**
* 售卖馒头(非线程安全)
*/
- (void)sellfood {
while (1) {
//如果还有馒头,继续卖
if (self.bunCount > 0) {
self.bunCount --;
NSLog(@"%@", [NSString stringWithFormat:@"剩余馒头数:%ld 窗口:%@", self.bunCount, [NSThread currentThread].name]);
[NSThread sleepForTimeInterval:0.2];
}
//如果已卖完,关闭售卖窗口
else {
NSLog(@"所有馒头已售完");
break;
}
}
}
```
打印 部分 发现数据错乱了
```
2019-12-04 17:46:43.055837+0800 多线程test[34233:1942363] 剩余馒头数:18 窗口:食堂售饭窗口1
2019-12-04 17:46:43.055837+0800 多线程test[34233:1942364] 剩余馒头数:17 窗口:食堂售饭窗口2
2019-12-04 17:46:43.260489+0800 多线程test[34233:1942363] 剩余馒头数:16 窗口:食堂售饭窗口1
2019-12-04 17:46:43.260518+0800 多线程test[34233:1942364] 剩余馒头数:16 窗口:食堂售饭窗口2
2019-12-04 17:46:43.465406+0800 多线程test[34233:1942363] 剩余馒头数:15 窗口:食堂售饭窗口1
2019-12-04 17:46:43.465406+0800 多线程test[34233:1942364] 剩余馒头数:15 窗口:食堂售饭窗口2
2019-12-04 17:46:43.669683+0800 多线程test[34233:1942364] 剩余馒头数:13 窗口:食堂售饭窗口2
2019-12-04 17:46:43.669742+0800 多线程test[34233:1942363] 剩余馒头数:14 窗口:食堂售饭窗口1
2019-12-04 17:46:43.870421+0800 多线程test[34233:1942364] 剩余馒头数:12 窗口:食堂售饭窗口2
2019-12-04 17:46:43.870421+0800 多线程test[34233:1942363] 剩余馒头数:12 窗口:食堂售饭窗口1
2019-12-04 17:46:44.071775+0800 多线程test[34233:1942363] 剩余馒头数:10 窗口:食堂售饭窗口1
2019-12-04 17:46:44.071779+0800 多线程test[34233:1942364] 剩余馒头数:11 窗口:食堂售饭窗口2
2019-12-04 17:46:44.276647+0800 多线程test[34233:1942364] 剩余馒头数:9 窗口:食堂售饭窗口2
2019-12-04 17:46:44.276650+0800 多线程test[34233:1942363] 剩余馒头数:8 窗口:食堂售饭窗口1
2019-12-04 17:46:44.478485+0800 多线程test[34233:1942363] 剩余馒头数:7 窗口:食堂售饭窗口1
2019-12-04 17:46:44.478493+0800 多线程test[34233:1942364] 剩余馒头数:6 窗口:食堂售饭窗口2
2019-12-04 17:46:44.682709+0800 多线程test[34233:1942364] 剩余馒头数:5 窗口:食堂售饭窗口2
2019-12-04 17:46:44.682711+0800 多线程test[34233:1942363] 剩余馒头数:4 窗口:食堂售饭窗口1
2019-12-04 17:46:44.887120+0800 多线程test[34233:1942364] 剩余馒头数:3 窗口:食堂售饭窗口2
2019-12-04 17:46:44.887146+0800 多线程test[34233:1942363] 剩余馒头数:3 窗口:食堂售饭窗口1
```
线程安全解决方案:可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。
```
/**
* 售卖馒头(线程安全)
*/
- (void)sellfood {
while (1) {
@synchronized (self) {
//如果还有馒头,继续卖
if (self.bunCount > 0) {
self.bunCount --;
NSLog(@"%@", [NSString stringWithFormat:@"剩余馒头数:%ld 窗口:%@", self.bunCount, [NSThread currentThread].name]);
[NSThread sleepForTimeInterval:0.2];
}
//如果已卖完,关闭售卖窗口
else {
NSLog(@"所有馒头已售完");
break;
}
}
}
}
```
打印发现 馒头数对上了 这就是考虑了线程安全
```
2019-12-04 17:53:57.698531+0800 多线程test[34292:1946779] 剩余馒头数:22 窗口:食堂售饭窗口2
2019-12-04 17:53:57.900181+0800 多线程test[34292:1946779] 剩余馒头数:21 窗口:食堂售饭窗口2
2019-12-04 17:53:58.100877+0800 多线程test[34292:1946779] 剩余馒头数:20 窗口:食堂售饭窗口2
2019-12-04 17:53:58.302771+0800 多线程test[34292:1946779] 剩余馒头数:19 窗口:食堂售饭窗口2
2019-12-04 17:53:58.506626+0800 多线程test[34292:1946779] 剩余馒头数:18 窗口:食堂售饭窗口2
2019-12-04 17:53:58.711036+0800 多线程test[34292:1946778] 剩余馒头数:17 窗口:食堂售饭窗口1
2019-12-04 17:53:58.915151+0800 多线程test[34292:1946778] 剩余馒头数:16 窗口:食堂售饭窗口1
2019-12-04 17:53:59.119483+0800 多线程test[34292:1946778] 剩余馒头数:15 窗口:食堂售饭窗口1
2019-12-04 17:53:59.322588+0800 多线程test[34292:1946778] 剩余馒头数:14 窗口:食堂售饭窗口1
2019-12-04 17:53:59.524112+0800 多线程test[34292:1946778] 剩余馒头数:13 窗口:食堂售饭窗口1
2019-12-04 17:53:59.728176+0800 多线程test[34292:1946778] 剩余馒头数:12 窗口:食堂售饭窗口1
2019-12-04 17:53:59.928589+0800 多线程test[34292:1946779] 剩余馒头数:11 窗口:食堂售饭窗口2
2019-12-04 17:54:00.133544+0800 多线程test[34292:1946778] 剩余馒头数:10 窗口:食堂售饭窗口1
2019-12-04 17:54:00.334732+0800 多线程test[34292:1946779] 剩余馒头数:9 窗口:食堂售饭窗口2
2019-12-04 17:54:00.539133+0800 多线程test[34292:1946779] 剩余馒头数:8 窗口:食堂售饭窗口2
2019-12-04 17:54:00.739621+0800 多线程test[34292:1946779] 剩余馒头数:7 窗口:食堂售饭窗口2
2019-12-04 17:54:00.941873+0800 多线程test[34292:1946778] 剩余馒头数:6 窗口:食堂售饭窗口1
2019-12-04 17:54:01.146433+0800 多线程test[34292:1946778] 剩余馒头数:5 窗口:食堂售饭窗口1
2019-12-04 17:54:01.351048+0800 多线程test[34292:1946778] 剩余馒头数:4 窗口:食堂售饭窗口1
2019-12-04 17:54:01.552734+0800 多线程test[34292:1946779] 剩余馒头数:3 窗口:食堂售饭窗口2
2019-12-04 17:54:01.788531+0800 多线程test[34292:1946779] 剩余馒头数:2 窗口:食堂售饭窗口2
2019-12-04 17:54:01.990576+0800 多线程test[34292:1946779] 剩余馒头数:1 窗口:食堂售饭窗口2
2019-12-04 17:54:02.195163+0800 多线程test[34292:1946779] 剩余馒头数:0 窗口:食堂售饭窗口2
```
网友评论