iOS多线程篇:NSThread简单介绍和使用
一、什么是NSThread
NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,
需要手动管理线程的生命周期,处理线程同步等问题。
二、NSThread方法介绍
1)动态创建
|
1
|
NSThread * newThread = [[NSThread alloc]initWithTarget:self |
动态方法返回一个新的thread对象,需要调用start方法来启动线程
2)静态创建
|
1
|
[NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil]; |
由于静态方法没有返回值,如果需要获取新创建的thread,需要在selector中调用获取当前线程的方法
3)线程开启
|
1
|
[newThread start]; |
4)线程暂停
|
1
2
|
[NSThread sleepForTimeInterval:1.0]; (以暂停一秒为例)[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; |
NSThread的暂停会有阻塞当前线程的效果
5)线程取消
|
1
|
[newThread cancel]; |
取消线程并不会马上停止并退出线程,仅仅只作(线程是否需要退出)状态记录
6)线程停止
|
1
|
[NSThread exit]; |
停止方法会立即终止除主线程以外所有线程(无论是否在执行任务)并退出,需要在掌控所有线程状态的情况下调用此方法,
否则可能会导致内存问题。
7)获取当前线程
|
1
|
[NSThread currentThread]; |
8)获取主线程
|
1
|
[NSThread mainThread]; |
9)线程优先级设置
iOS 8以前使用
|
1
|
[NSThread setThreadPriority:1.0]; |
这个方法的优先级的数值设置让人困惑,因为你不知道你应该设置多大的值是比较合适的,因此在iOS8之后,threadPriority添加了
一句注释:To be deprecated; use qualityOfService below
意思就是iOS 8以后推荐使用qualityOfService属性,通过量化的优先级枚举值来设置
qualityOfService的枚举值如下:
NSQualityOfServiceUserInteractive:最高优先级,用于用户交互事件
NSQualityOfServiceUserInitiated:次高优先级,用于用户需要马上执行的事件
NSQualityOfServiceDefault:默认优先级,主线程和没有设置优先级的线程都默认为这个优先级
NSQualityOfServiceUtility:普通优先级,用于普通任务
NSQualityOfServiceBackground:最低优先级,用于不重要的任务
比如给线程设置次高优先级:
|
1
|
[newThread setQualityOfService:NSQualityOfServiceUserInitiated]; |
三、线程间通信
常用的有三种:
1、指定当前线程执行操作
|
1
2
3
|
[self performSelector:@selector(threadRun)];[self performSelector:@selector(threadRun) withObject:nil];[self performSelector:@selector(threadRun) withObject:nil afterDelay:2.0]; |
2、(在其他线程中)指定主线程执行操作
|
1
|
[self performSelectorOnMainThread:@selector(threadRun) withObject:nil |
注意:更新UI要在主线程中进行
3、(在主线程中)指定其他线程执行操作
|
1
2
|
[self performSelector:@selector(threadRun) onThread:newThread//这里指定为某个线程[self performSelectorInBackground:@selector(threadRun) withObject:nil];//这里指定为后台线程 |
四、线程同步
线程和其他线程可能会共享一些资源,当多个线程同时读写同一份共享资源的时候,可能会引起冲突。线程同步是指是指在一定的时间内只允许
某一个线程访问某个资源
iOS实现线程加锁有NSLock和@synchronized两种方式。
五、线程的创建和使用实例:模拟售票
情景:某演唱会门票发售,在广州和北京均开设窗口进行销售,以下是代码实现
先监听线程退出的通知,以便知道线程什么时候退出
|
1
|
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) |
设置演唱会的门票数量
|
1
|
_ticketCount = 50; |
新建两个子线程(代表两个窗口同时销售门票)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
NSThread * window1 = [[NSThread alloc]initWithTarget:selfwindow1.name = @"北京售票窗口";[window1 start];NSThread * window2 = [[NSThread alloc]initWithTarget:selfwindow2.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; }}} |
执行结果:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
2016-04-06 19:25:36.637 MutiThread[4705:1371666] 剩余票数:9 窗口:广州售票窗口2016-04-06 19:25:36.637 MutiThread[4705:1371665] 剩余票数:8 窗口:北京售票窗口2016-04-06 19:25:36.839 MutiThread[4705:1371666] 剩余票数:7 窗口:广州售票窗口2016-04-06 19:25:36.839 MutiThread[4705:1371665] 剩余票数:7 窗口:北京售票窗口2016-04-06 19:25:37.045 MutiThread[4705:1371666] 剩余票数:5 窗口:广州售票窗口2016-04-06 19:25:37.045 MutiThread[4705:1371665] 剩余票数:6 窗口:北京售票窗口2016-04-06 19:25:37.250 MutiThread[4705:1371665] 剩余票数:4 窗口:北京售票窗口2016-04-06 19:25:37.250 MutiThread[4705:1371666] 剩余票数:4 窗口:广州售票窗口2016-04-06 19:25:37.456 MutiThread[4705:1371666] 剩余票数:2 窗口:广州售票窗口2016-04-06 19:25:37.456 MutiThread[4705:1371665] 剩余票数:3 窗口:北京售票窗口2016-04-06 19:25:37.661 MutiThread[4705:1371665] 剩余票数:1 窗口:北京售票窗口2016-04-06 19:25:37.661 MutiThread[4705:1371666] 剩余票数:1 窗口:广州售票窗口2016-04-06 19:25:37.866 MutiThread[4705:1371665] 剩余票数:0 窗口:北京售票窗口2016-04-06 19:25:37.867 MutiThread[4705:1371666] <nsthread: 0x7fdc91e289f0>2016-04-06 19:25:38.070 MutiThread[4705:1371665] <nsthread: 0x7fdc91e24d60> |
可以看到,票的销售过程中出现了剩余数量错乱的情况,这就是前面提到的线程同步问题。
售票是一个典型的需要线程同步的场景,由于售票渠道有很多,而票的资源是有限的,当多个渠道在短时间内卖出大量
的票的时候,如果没有同步机制来管理票的数量,将会导致票的总数和售出票数对应不上的错误。
我们在售票的过程中给票加上同步锁:同一时间内,只有一个线程能对票的数量进行操作,当操作完成之后,其他线程
才能继续对票的数量进行操作。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
- (void)saleTicket { while (1) { @synchronized(self) { //如果还有票,继续售卖 if (_ticketCount > 0) { _ticketCount --; NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]); [NSThread sleepForTimeInterval:0.2]; } //如果已卖完,关闭售票窗口 else { break; } } }} |
运行结果:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
2016-04-06 19:31:27.913 MutiThread[4718:1406865] 剩余票数:11 窗口:北京售票窗口2016-04-06 19:31:28.115 MutiThread[4718:1406866] 剩余票数:10 窗口:广州售票窗口2016-04-06 19:31:28.317 MutiThread[4718:1406865] 剩余票数:9 窗口:北京售票窗口2016-04-06 19:31:28.522 MutiThread[4718:1406866] 剩余票数:8 窗口:广州售票窗口2016-04-06 19:31:28.728 MutiThread[4718:1406865] 剩余票数:7 窗口:北京售票窗口2016-04-06 19:31:28.929 MutiThread[4718:1406866] 剩余票数:6 窗口:广州售票窗口2016-04-06 19:31:29.134 MutiThread[4718:1406865] 剩余票数:5 窗口:北京售票窗口2016-04-06 19:31:29.339 MutiThread[4718:1406866] 剩余票数:4 窗口:广州售票窗口2016-04-06 19:31:29.545 MutiThread[4718:1406865] 剩余票数:3 窗口:北京售票窗口2016-04-06 19:31:29.751 MutiThread[4718:1406866] 剩余票数:2 窗口:广州售票窗口2016-04-06 19:31:29.952 MutiThread[4718:1406865] 剩余票数:1 窗口:北京售票窗口2016-04-06 19:31:30.158 MutiThread[4718:1406866] 剩余票数:0 窗口:广州售票窗口2016-04-06 19:31:30.363 MutiThread[4718:1406866] <nsthread: 0x7ff0c1637320>2016-04-06 19:31:30.363 MutiThread[4718:1406865] <nsthread: 0x7ff0c1420cb0> |
可以看到,票的数量没有出现错乱的情况。
线程的持续运行和退出
我们注意到,线程启动后,执行saleTicket完毕后就马上退出了,怎样能让线程一直运行呢(窗口一直开放,
可以随时指派其卖演唱会的门票的任务),答案就是给线程加上runLoop
|
1
2
|
//先监听线程退出的通知,以便知道线程什么时候退出[[NSNotificationCenter defaultCenter]addObserver:self |
|
1
2
|
//设置演唱会的门票数量_ticketCount = 50; |
新建两个子线程(代表两个窗口同时销售门票)
|
1
2
3
4
|
NSThread * window1 = [[NSThread alloc]initWithTarget:self[window1 start];NSThread * window2 = [[NSThread alloc]initWithTarget:self[window2 start]; |
接着我们给线程创建一个runLoop
|
1
2
3
4
5
6
7
8
9
10
|
- (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]]; //自定义运行时间} |
然后就可以指派任务给线程了,这里我们让两个线程都执行相同的任务(售票)
|
1
2
|
[self performSelector:@selector(saleTicket) onThread:window1[self performSelector:@selector(saleTicket) onThread:window2 |
运行结果:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
2016-04-06 19:43:22.585 MutiThread[4762:1478200] 剩余票数:11 窗口:北京售票窗口2016-04-06 19:43:22.788 MutiThread[4762:1478201] 剩余票数:10 窗口:广州售票窗口2016-04-06 19:43:22.993 MutiThread[4762:1478200] 剩余票数:9 窗口:北京售票窗口2016-04-06 19:43:23.198 MutiThread[4762:1478201] 剩余票数:8 窗口:广州售票窗口2016-04-06 19:43:23.404 MutiThread[4762:1478200] 剩余票数:7 窗口:北京售票窗口2016-04-06 19:43:23.609 MutiThread[4762:1478201] 剩余票数:6 窗口:广州售票窗口2016-04-06 19:43:23.810 MutiThread[4762:1478200] 剩余票数:5 窗口:北京售票窗口2016-04-06 19:43:24.011 MutiThread[4762:1478201] 剩余票数:4 窗口:广州售票窗口2016-04-06 19:43:24.216 MutiThread[4762:1478200] 剩余票数:3 窗口:北京售票窗口2016-04-06 19:43:24.422 MutiThread[4762:1478201] 剩余票数:2 窗口:广州售票窗口2016-04-06 19:43:24.628 MutiThread[4762:1478200] 剩余票数:1 窗口:北京售票窗口2016-04-06 19:43:24.833 MutiThread[4762:1478201] 剩余票数:0 窗口:广州售票窗口2016-04-06 19:43:25.039 MutiThread[4762:1478201] |
可以看到,当票卖完后,两个线程并没有退出,仍在继续运行,当到达指定时间后,线程2退出了,
如果需要让线程1退出,需要我们手动管理。
比如我们让线程完成任务(售票)后自行退出,可以这样操作
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
- (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()); } } } }} |
运行结果:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
2016-04-06 20:08:38.287 MutiThread[4927:1577193] 剩余票数:10 窗口:北京售票窗口2016-04-06 20:08:38.489 MutiThread[4927:1577194] 剩余票数:9 窗口:广州售票窗口2016-04-06 20:08:38.690 MutiThread[4927:1577193] 剩余票数:8 窗口:北京售票窗口2016-04-06 20:08:38.892 MutiThread[4927:1577194] 剩余票数:7 窗口:广州售票窗口2016-04-06 20:08:39.094 MutiThread[4927:1577193] 剩余票数:6 窗口:北京售票窗口2016-04-06 20:08:39.294 MutiThread[4927:1577194] 剩余票数:5 窗口:广州售票窗口2016-04-06 20:08:39.499 MutiThread[4927:1577193] 剩余票数:4 窗口:北京售票窗口2016-04-06 20:08:39.700 MutiThread[4927:1577194] 剩余票数:3 窗口:广州售票窗口2016-04-06 20:08:39.905 MutiThread[4927:1577193] 剩余票数:2 窗口:北京售票窗口2016-04-06 20:08:40.106 MutiThread[4927:1577194] 剩余票数:1 窗口:广州售票窗口2016-04-06 20:08:40.312 MutiThread[4927:1577193] 剩余票数:0 窗口:北京售票窗口2016-04-06 20:08:40.516 MutiThread[4927:1577194] 售卖完毕2016-04-06 20:08:40.516 MutiThread[4927:1577193] 售卖完毕2016-04-06 20:08:40.517 MutiThread[4927:1577193]2016-04-06 20:08:40.517 MutiThread[4927:1577194] |
如果确定两个线程都是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
iOS多线程篇:NSThread简单介绍和使用的更多相关文章
- iOS开发多线程篇—NSOperation简单介绍
iOS开发多线程篇—NSOperation简单介绍 一.NSOperation简介 1.简单说明 NSOperation的作⽤:配合使用NSOperation和NSOperationQueue也能实现 ...
- 【iOS开发】NSOperation简单介绍
iOS开发多线程篇—NSOperation简单介绍 一.NSOperation简介 1.简单说明 NSOperation的作⽤:配合使用NSOperation和NSOperationQueue也能实现 ...
- iOS开发数据库篇—FMDB简单介绍
iOS开发数据库篇—FMDB简单介绍 一.简单说明 1.什么是FMDB FMDB是iOS平台的SQLite数据库框架 FMDB以OC的方式封装了SQLite的C语言API 2.FMDB的优点 使用起来 ...
- iOS开发UI篇—UITabBarController简单介绍
iOS开发UI篇—UITabBarController简单介绍 一.简单介绍 UITabBarController和UINavigationController类似,UITabBarControlle ...
- iOS开发UI篇—Modal简单介绍
iOS开发UI篇—Modal简单介绍 一.简单介绍 除了push之外,还有另外一种控制器的切换方式,那就是Modal 任何控制器都能通过Modal的形式展⽰出来 Modal的默认效果:新控制器从屏幕的 ...
- iOS开发数据库篇—SQLite简单介绍
iOS开发数据库篇—SQLite简单介绍 一.离线缓存 在项目开发中,通常都需要对数据进行离线缓存的处理,如新闻数据的离线缓存等. 说明:离线缓存一般都是把数据保存到项目的沙盒中.有以下几种方式 (1 ...
- iOS开发UI篇—Kvc简单介绍
ios开发UI篇—Kvc简单介绍 一.KVC简单介绍 KVC key valued coding 键值编码 KVC通过键值间接编码 补充: 与KVC相对的时KVO,即key valued observ ...
- iOS开发UI篇—UIWindow简单介绍
iOS开发UI篇—UIWindow简单介绍 一.简单介绍 UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow iOS程序启动完毕后,创建的第一个视图控件就是UIWi ...
- iOS开发UI篇—Quartz2D简单介绍
iOS开发UI篇—Quartz2D简单介绍 一.什么是Quartz2D Quartz 2D是⼀个二维绘图引擎,同时支持iOS和Mac系统 Quartz 2D能完成的工作: 绘制图形 : 线条\三角形\ ...
- iOS开发拓展篇—UIDynamic(简单介绍)
iOS开发拓展篇—UIDynamic(简单介绍) 一.简单介绍 1.什么是UIDynamic UIDynamic是从iOS 7开始引入的一种新技术,隶属于UIKit框架 可以认为是一种物理引擎,能模拟 ...
随机推荐
- maven配置详解
什么是pom? pom作为项目对象模型.通过xml表示maven项目,使用pom.xml来实现.主要描述了项目:包括配置文件:开发者需要遵循的规则,缺陷管理系统,组织和licenses,项目的 ...
- ORACLE数据库学习之逻辑结构
逻辑结构 数据库逻辑结构包含表空间.段.范围(extent).数据块和模式对象. (一)表空间 一个数据库划分为一个或多个逻辑单位,该逻辑单位称为表空间类似于sybase下的设备.(TABLES ...
- 如何向android studio中导入第三方类库
下面分两种情况介绍一下如何导入第三方类库. 1.对于jar的类库,直接复制进libs目录,然后把jar复制进去,然后File->Project Structure,然后选中主module的名称, ...
- socket系列之socket服务端与客户端如何通信
上面已经分别介绍了ServerSocket跟Socket的工作步骤,并且从应用层往系统底层剖析其运作原理,我们清楚了他们各自的一块,现在我们将把他们结合起来,看看他们是如何通信的,并详细讨论一下他们之 ...
- shell的追踪与调试选项
选项: -n :不执行shell脚本,只检查语法问题.没有问题则没有输出. -v :执行shell脚本前,现将shell脚本的命令输出到屏幕上.输出一段,执行一段. -x :将使用到的所有shell脚 ...
- 剑指Offer——网易笔试之不要二——欧式距离的典型应用
剑指Offer--网易笔试之不要二--欧式距离的典型应用 前言 欧几里得度量(euclidean metric)(也称欧氏距离)是一个通常采用的距离定义,指在m维空间中两个点之间的真实距离,或者向量的 ...
- Android开发学习之路--UI之基本布局
上一篇文章中主要介绍了ui的控件,这里就学习下布局吧.android的基本布局在layout下主要如图: 从上图可以看出有FrameLayout(单帧布局),LinearLayout(线性布局),Ta ...
- (七十)Xcode5及以上对于状态栏和导航栏样式的设定方法
[状态栏] 在Xcode5以前,状态栏是通过UIApplication单例来管理的,而在此后,默认情况下状态栏通过控制器来管理,而且如果控制器有NavigationController,那么设置状态栏 ...
- DB 查询分析器 6.04 发布 ,本人为之撰写的相关技术文章达78篇
DB查询分析器 6.04 发布,本人为之撰写的相关技术文章达78篇 中国本土程序员马根峰(CSDN专访马根峰:海量数据处理与分析大师的中国本土程序员 http://www.csdn.net/artic ...
- 关于会话、进程、请求的几个常用SQL
1.检查自己的SID SELECT sid FROM v$session WHERE sid = (SELECT sid FROM v$mystat WHERE rownum = 1); 2. 几个I ...