一、什么是NSThread

NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,

需要手动管理线程的生命周期,处理线程同步等问题。

二、NSThread方法介绍

1)动态创建

1
NSThread * newThread = [[NSThread alloc]initWithTarget:self 
selector:@selector(threadRun) object:nil];

动态方法返回一个新的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 
waitUntilDone:YES];

注意:更新UI要在主线程中进行

3、(在主线程中)指定其他线程执行操作

1
2
[self performSelector:@selector(threadRun) onThread:newThread 
withObject:nil waitUntilDone:YES]; 
//这里指定为某个线程
[self performSelectorInBackground:@selector(threadRun) withObject:nil];
//这里指定为后台线程

四、线程同步

线程和其他线程可能会共享一些资源,当多个线程同时读写同一份共享资源的时候,可能会引起冲突。线程同步是指是指在一定的时间内只允许

某一个线程访问某个资源

iOS实现线程加锁有NSLock和@synchronized两种方式。

五、线程的创建和使用实例:模拟售票

情景:某演唱会门票发售,在广州和北京均开设窗口进行销售,以下是代码实现

先监听线程退出的通知,以便知道线程什么时候退出

1
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice)
name:NSThreadWillExitNotification object:nil];

设置演唱会的门票数量

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: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;
    }
}
}

执行结果:

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>
{number = 3, name = 广州售票窗口} Will Exit
2016-04-06 19:25:38.070 MutiThread[4705:1371665] <nsthread: 0x7fdc91e24d60>
{number = 2, name = 北京售票窗口} Will Exit</nsthread: 0x7fdc91e24d60>
</nsthread: 0x7fdc91e289f0>

可以看到,票的销售过程中出现了剩余数量错乱的情况,这就是前面提到的线程同步问题。

售票是一个典型的需要线程同步的场景,由于售票渠道有很多,而票的资源是有限的,当多个渠道在短时间内卖出大量

的票的时候,如果没有同步机制来管理票的数量,将会导致票的总数和售出票数对应不上的错误。

我们在售票的过程中给票加上同步锁:同一时间内,只有一个线程能对票的数量进行操作,当操作完成之后,其他线程

才能继续对票的数量进行操作。

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>
{number = 3, name = 广州售票窗口} Will Exit
2016-04-06 19:31:30.363 MutiThread[4718:1406865] <nsthread: 0x7ff0c1420cb0>
{number = 2, name = 北京售票窗口} Will Exit</nsthread: 0x7ff0c1420cb0>
</nsthread: 0x7ff0c1637320>

可以看到,票的数量没有出现错乱的情况。

线程的持续运行和退出

我们注意到,线程启动后,执行saleTicket完毕后就马上退出了,怎样能让线程一直运行呢(窗口一直开放,

可以随时指派其卖演唱会的门票的任务),答案就是给线程加上runLoop

1
2
 
//先监听线程退出的通知,以便知道线程什么时候退出
[[NSNotificationCenter defaultCenter]addObserver:self 
selector:@selector(threadExitNotice)
name:NSThreadWillExitNotification object:nil];
1
2
//设置演唱会的门票数量
_ticketCount = 50;

新建两个子线程(代表两个窗口同时销售门票)

1
2
3
4
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

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 
withObject:nil waitUntilDone:NO];
[self performSelector:@selector(saleTicket) onThread:window2 
withObject:nil waitUntilDone:NO];

运行结果:

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] 
<nsthread: 0x7fe0d3c24360>{number = 3, name = 广州售票窗口} 
Will Exit</nsthread: 0x7fe0d3c24360>

可以看到,当票卖完后,两个线程并没有退出,仍在继续运行,当到达指定时间后,线程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] 
<nsthread: 0x7fb719d54000>{number = 2, name = 北京售票窗口} Will Exit
2016-04-06 20:08:40.517 MutiThread[4927:1577194] 
<nsthread: 0x7fb719d552f0>{number = 3, name = 广州售票窗口} 
Will Exit</nsthread: 0x7fb719d552f0></nsthread: 0x7fb719d54000>

如果确定两个线程都是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

NSTheard 详解的更多相关文章

  1. Linq之旅:Linq入门详解(Linq to Objects)

    示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...

  2. 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)

    一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...

  3. EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

    前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...

  4. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  5. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  6. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

  7. Git初探--笔记整理和Git命令详解

    几个重要的概念 首先先明确几个概念: WorkPlace : 工作区 Index: 暂存区 Repository: 本地仓库/版本库 Remote: 远程仓库 当在Remote(如Github)上面c ...

  8. Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

    Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...

  9. Node.js npm 详解

    一.npm简介 安装npm请阅读我之前的文章Hello Node中npm安装那一部分,不过只介绍了linux平台,如果是其它平台,有前辈写了更加详细的介绍. npm的全称:Node Package M ...

随机推荐

  1. Codeforces #263 div2 解题报告

    比赛链接:http://codeforces.com/contest/462 这次比赛的时候,刚刚注冊的时候非常想好好的做一下,可是网上喝了个小酒之后.也就迷迷糊糊地看了题目,做了几题.一觉醒来发现r ...

  2. es6 - array for-chrome

    // 去重复 Array.from(new Set([1, 1, 2, 3])); // [1, 2, 3] console.log(Array.from(new Set([1, 1, 2, 3])) ...

  3. vue笔记二

    七.列表渲染 1.示例 <ul id="example-2"> <li v-for="(item, index) in items"> ...

  4. 【IOS】mac终端运行.sh文件总是提示permission denied

    如果我目录jni有一个list.sh文件 我直接 nxgametekiMacBook-Air:jni luonan$  ./list.sh ../../Classes 提示 permission de ...

  5. HDOJ 1534 Schedule Problem 差分约束

    差分约数: 求满足不等式条件的尽量小的值---->求最长路---->a-b>=c----> b->a (c) Schedule Problem Time Limit: 2 ...

  6. 杭电 HDU 2717 Catch That Cow

    Catch That Cow Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) T ...

  7. CentOS6.8 搭建SVN并用钩子自动实现同步到web目录

    一 安装 yum install subversion 二 检查是否安装成功 svn --version 三 创建仓库目录 mkdir –p /home/svnroot/test 四 创建项目 svn ...

  8. MySQL + Amoeba 负载均衡、主从备份方案

    1.  基本环境 4台内网虚拟机的操作系统都是ubuntu-14.04.4 64位 IP为:192.168.169.11.192.168.169.12.192.168.169.13.192.168.1 ...

  9. mongoDB 高级查询语法

    http://www.cnblogs.com/ITAres/articles/2084794.html本文参考自官方的手册:http://www.mongodb.org/display/DOCS/Ad ...

  10. 下拉刷新Listview(8.30)

    Android-PullToRefresh 1项目托管地址: https://github.com/bavariama1/Android-PullToRefresh 2 快速开始教程:https:// ...