iOS多线程篇:NSThread简单介绍和使用
一、什么是NSThread
NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,
需要手动管理线程的生命周期,处理线程同步等问题。
二、NSThread方法介绍
1)动态创建
NSThread * newThread = [[NSThread alloc]initWithTarget:self selector:@selector(threadRun) object:nil];
动态方法返回一个新的thread对象,需要调用start方法来启动线程
2)静态创建
[NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil];
由于静态方法没有返回值,如果需要获取新创建的thread,需要在selector中调用获取当前线程的方法
3)线程开启
[newThread start];
4)线程暂停
[NSThread sleepForTimeInterval:1.0]; (以暂停一秒为例) [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
NSThread的暂停会有阻塞当前线程的效果
5)线程取消
[newThread cancel];
取消线程并不会马上停止并退出线程,仅仅只作(线程是否需要退出)状态记录
6)线程停止
[NSThread exit];
停止方法会立即终止除主线程以外所有线程(无论是否在执行任务)并退出,需要在掌控所有线程状态的情况下调用此方法,
否则可能会导致内存问题。
7)获取当前线程
[NSThread currentThread];
8)获取主线程
[NSThread mainThread];
9)线程优先级设置
iOS 8以前使用
[NSThread setThreadPriority:1.0];
这个方法的优先级的数值设置让人困惑,因为你不知道你应该设置多大的值是比较合适的,因此在iOS8之后,threadPriority添加了
一句注释:To be deprecated; use qualityOfService below
意思就是iOS 8以后推荐使用qualityOfService属性,通过量化的优先级枚举值来设置
作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这有个iOS交流群:642363427,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术!
qualityOfService的枚举值如下:
-
NSQualityOfServiceUserInteractive:最高优先级,用于用户交互事件
-
NSQualityOfServiceUserInitiated:次高优先级,用于用户需要马上执行的事件
-
NSQualityOfServiceDefault:默认优先级,主线程和没有设置优先级的线程都默认为这个优先级
-
NSQualityOfServiceUtility:普通优先级,用于普通任务
-
NSQualityOfServiceBackground:最低优先级,用于不重要的任务
比如给线程设置次高优先级:
[newThread setQualityOfService:NSQualityOfServiceUserInitiated];
三、线程间通信
常用的有三种:
1、指定当前线程执行操作
[self performSelector:@selector(threadRun)]; [self performSelector:@selector(threadRun) withObject:nil]; [self performSelector:@selector(threadRun) withObject:nil afterDelay:2.0];
2、(在其他线程中)指定主线程执行操作
[self performSelectorOnMainThread:@selector(threadRun) withObject:nil waitUntilDone:YES];
注意:更新UI要在主线程中进行
3、(在主线程中)指定其他线程执行操作
[self performSelector:@selector(threadRun) onThread:newThread withObject:nil waitUntilDone:YES]; //这里指定为某个线程 [self performSelectorInBackground:@selector(threadRun) withObject:nil]; //这里指定为后台线程
四、线程同步
线程和其他线程可能会共享一些资源,当多个线程同时读写同一份共享资源的时候,可能会引起冲突。线程同步是指是指在一定的时间内只允许
某一个线程访问某个资源
iOS实现线程加锁有NSLock和@synchronized两种方式。
五、线程的创建和使用实例:模拟售票
情景:某演唱会门票发售,在广州和北京均开设窗口进行销售,以下是代码实现
先监听线程退出的通知,以便知道线程什么时候退出
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) name:NSThreadWillExitNotification object:nil];
设置演唱会的门票数量
_ticketCount = 50;
新建两个子线程(代表两个窗口同时销售门票)
NSThread * window1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; window1.name = @"北京售票窗口"; [window1 start]; NSThread * window2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; window2.name = @"广州售票窗口"; [window2 start]; 线程启动后,执行saleTicket,执行完毕后就会退出,为了模拟持续售票的过程, 我们需要给它加一个循环 - (void)saleTicket { while (1) { //如果还有票,继续售卖 if (_ticketCount > 0) { _ticketCount --; NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]); [NSThread sleepForTimeInterval:0.2]; } //如果已卖完,关闭售票窗口 else { break; } } }
可以看到,票的销售过程中出现了剩余数量错乱的情况,这就是前面提到的线程同步问题。
售票是一个典型的需要线程同步的场景,由于售票渠道有很多,而票的资源是有限的,当多个渠道在短时间内卖出大量
的票的时候,如果没有同步机制来管理票的数量,将会导致票的总数和售出票数对应不上的错误。
我们在售票的过程中给票加上同步锁:同一时间内,只有一个线程能对票的数量进行操作,当操作完成之后,其他线程
才能继续对票的数量进行操作。
- (void)saleTicket { while (1) { @synchronized(self) { //如果还有票,继续售卖 if (_ticketCount > 0) { _ticketCount --; NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]); [NSThread sleepForTimeInterval:0.2]; } //如果已卖完,关闭售票窗口 else { break; } } } }
线程的持续运行和退出
我们注意到,线程启动后,执行saleTicket完毕后就马上退出了,怎样能让线程一直运行呢(窗口一直开放,
可以随时指派其卖演唱会的门票的任务),答案就是给线程加上runLoop
//先监听线程退出的通知,以便知道线程什么时候退出 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) name:NSThreadWillExitNotification object:nil];
//设置演唱会的门票数量 _ticketCount = 50;
新建两个子线程(代表两个窗口同时销售门票)
NSThread * window1 = [[NSThread alloc]initWithTarget:self selector:@selector(thread1) object:nil]; [window1 start]; NSThread * window2 = [[NSThread alloc]initWithTarget:self selector:@selector(thread2) object:nil]; [window2 start];
接着我们给线程创建一个runLoop
- (void)thread1 { [NSThread currentThread].name = @"北京售票窗口"; NSRunLoop * runLoop1 = [NSRunLoop currentRunLoop]; [runLoop1 runUntilDate:[NSDate date]]; //一直运行 } - (void)thread2 { [NSThread currentThread].name = @"广州售票窗口"; NSRunLoop * runLoop2 = [NSRunLoop currentRunLoop]; [runLoop2 runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //自定义运行时间 }
然后就可以指派任务给线程了,这里我们让两个线程都执行相同的任务(售票)
[self performSelector:@selector(saleTicket) onThread:window1 withObject:nil waitUntilDone:NO]; [self performSelector:@selector(saleTicket) onThread:window2 withObject:nil waitUntilDone:NO];
如果需要让线程1退出,需要我们手动管理。
比如我们让线程完成任务(售票)后自行退出,可以这样操作
- (void)saleTicket { while (1) { @synchronized(self) { //如果还有票,继续售卖 if (_ticketCount > 0) { _ticketCount --; NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]); [NSThread sleepForTimeInterval:0.2]; } //如果已卖完,关闭售票窗口 else { if ([NSThread currentThread].isCancelled) { break; }else { NSLog(@"售卖完毕"); //给当前线程标记为取消状态 [[NSThread currentThread] cancel]; //停止当前线程的runLoop CFRunLoopStop(CFRunLoopGetCurrent()); } } } } }
如果确定两个线程都是isCancelled状态,可以调用[NSThread exit]方法来终止线程。
NSThread
- 一个NSThread对象就代表一条线程
- NSThread会在执行完任务函数是被自动收回
- 一些常用的函数
//创建线程 NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector() object:nil]; //object存的是参数 //修改参数名 thread.name = @"我的多线程"; //获取主线程 NSThread *thread = [NSThread mainThread]; //创建线程并自动启动 [NSThread detachNewThreadSelector:@selector() toTarget:self withObject:nil]; //隐士创建 [self performSelectorInBackground:@selector() withObject:nil]; //获取当前线程 [NSThread currentThread] //启动线程 - (void)start; //阻塞(暂停)线程 + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 进入阻塞状态 //停止当前正在运行的进程 + (void)exit; // 进入死亡状态,一旦死亡则不能重启
资源共享
- 1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
- 当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
解决办法互斥锁
-
互斥锁使用格式
- @synchronized(锁对象) { // 需要锁定的代码 }
- 只用一把锁,多锁是无效的
-
互斥锁的优缺点
- 优点:能有效防止因多线程抢夺资源造成的数据安全问题
- 缺点:需要消耗大量的CPU资源
-
互斥锁的使用前提:多条线程抢夺同一块资源
- 互斥锁的示例代码
#import "ViewController.h" @interface ViewController () /** 售票机1*/ @property (strong,nonatomic) NSThread *threadOne; /** 售票机2*/ @property (strong,nonatomic) NSThread *threadTwo; /** 售票机3*/ @property (strong,nonatomic) NSThread *threadThree; /** 售票机4*/ @property (strong,nonatomic) NSThread *threadFour; /** 售票机5*/ @property (strong,nonatomic) NSThread *threadFive; /** 数量*/ @property (assign,nonatomic) NSInteger count; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //创建线程 self.threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"one"]; self.threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"two"]; self.threadThree = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"three"]; self.threadFour = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"four"]; self.threadFive = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"five"]; self.count = 100; } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //开始线程 [self.threadOne start]; [self.threadTwo start]; [self.threadThree start]; [self.threadFour start]; [self.threadFive start]; } - (void)run:(NSString *)param { while (1) { //self是锁对象 @synchronized(self) { if (self.count > 0) { _count--; NSLog(@"%zd-----%@",self.count,[NSThread currentThread]); } else { NSLog(@"卖完了----%@",[NSThread currentThread]); break; } } } } @end