iOS多线程篇:NSThread

移动开发
NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同步等问题。

[[165210]]

一、什么是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. [NSThread sleepForTimeInterval:1.0]; (以暂停一秒为例) 
  2. [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; 

NSThread的暂停会有阻塞当前线程的效果

5)线程取消

  1. [newThread cancel]; 

取消线程并不会马上停止并退出线程,仅仅只作(线程是否需要退出)状态记录

6)线程停止

  1. [NSThread exit]; 

停止方法会立即终止除主线程以外所有线程(无论是否在执行任务)并退出,需要在掌控所有线程状态的情况下调用此方法,否则可能会导致内存问题。

7)获取当前线程

  1. [NSThread currentThread]; 

8)获取主线程

 

  1. [NSThread mainThread]; 

 

9)线程优先级设置

iOS8以前使用

  1. [NSThread setThreadPriority:1.0]; 

这个方法的优先级的数值设置让人困惑,因为你不知道你应该设置多大的值是比较合适的,因此在iOS8之后,threadPriority添加了一句注释:To be deprecated; use qualityOfService below

意思就是iOS8以后推荐使用qualityOfService属性,通过量化的优先级枚举值来设置
qualityOfService的枚举值如下:
NSQualityOfServiceUserInteractive:***优先级,用于用户交互事件
NSQualityOfServiceUserInitiated:次高优先级,用于用户需要马上执行的事件
NSQualityOfServiceDefault:默认优先级,主线程和没有设置优先级的线程都默认为这个优先级
NSQualityOfServiceUtility:普通优先级,用于普通任务
NSQualityOfServiceBackground:***优先级,用于不重要的任务

比如给线程设置次高优先级:

  1. [newThread setQualityOfService:NSQualityOfServiceUserInitiated]; 

三、线程间通信

常用的有三种:

  1、指定当前线程执行操作

  1. [self performSelector:@selector(threadRun)]; 
  2. [self performSelector:@selector(threadRun) withObject:nil]; 
  3. [self performSelector:@selector(threadRun) withObject:nil afterDelay:2.0]; 

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

  1. [self performSelectorOnMainThread:@selector(threadRun) withObject:nil waitUntilDone:YES]; 

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

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

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

四、线程同步

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

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

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

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

  1. 先监听线程退出的通知,以便知道线程什么时候退出 
  2. [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) name:NSThreadWillExitNotification object:nil]; 
 
  1. 设置演唱会的门票数量 
  2.      _ticketCount = 50
  1. 新建两个子线程(代表两个窗口同时销售门票) 
  2.      NSThread * window1 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; 
  3.      window1.name = @"北京售票窗口"; 
  4.      [window1 start]; 
  5.  
  6.      NSThread * window2 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil]; 
  7.      window2.name = @"广州售票窗口"; 
  8.      [window2 start]; 
  1. 线程启动后,执行saleTicket,执行完毕后就会退出,为了模拟持续售票的过程,我们需要给它加一个循环 
  2.      - (void)saleTicket { 
  3.          while (1) { 
  4.              //如果还有票,继续售卖 
  5.              if (_ticketCount > 0) { 
  6.                  _ticketCount --; 
  7.                  NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]); 
  8.                  [NSThread sleepForTimeInterval:0.2]; 
  9.              } 
  10.              //如果已卖完,关闭售票窗口 
  11.              else { 
  12.                  break; 
  13.              } 
  14.          } 
  15.      } 
  1. 执行结果: 
  2.     2016-04-06 19:25:36.637 MutiThread[4705:1371666] 剩余票数:9 窗口:广州售票窗口 
  3.     2016-04-06 19:25:36.637 MutiThread[4705:1371665] 剩余票数:8 窗口:北京售票窗口 
  4.     2016-04-06 19:25:36.839 MutiThread[4705:1371666] 剩余票数:7 窗口:广州售票窗口 
  5.     2016-04-06 19:25:36.839 MutiThread[4705:1371665] 剩余票数:7 窗口:北京售票窗口 
  6.     2016-04-06 19:25:37.045 MutiThread[4705:1371666] 剩余票数:5 窗口:广州售票窗口 
  7.     2016-04-06 19:25:37.045 MutiThread[4705:1371665] 剩余票数:6 窗口:北京售票窗口 
  8.     2016-04-06 19:25:37.250 MutiThread[4705:1371665] 剩余票数:4 窗口:北京售票窗口 
  9.     2016-04-06 19:25:37.250 MutiThread[4705:1371666] 剩余票数:4 窗口:广州售票窗口 
  10.     2016-04-06 19:25:37.456 MutiThread[4705:1371666] 剩余票数:2 窗口:广州售票窗口 
  11.     2016-04-06 19:25:37.456 MutiThread[4705:1371665] 剩余票数:3 窗口:北京售票窗口 
  12.     2016-04-06 19:25:37.661 MutiThread[4705:1371665] 剩余票数:1 窗口:北京售票窗口 
  13.     2016-04-06 19:25:37.661 MutiThread[4705:1371666] 剩余票数:1 窗口:广州售票窗口 
  14.     2016-04-06 19:25:37.866 MutiThread[4705:1371665] 剩余票数:0 窗口:北京售票窗口 
  15.     2016-04-06 19:25:37.867 MutiThread[4705:1371666] <NSThread: 0x7fdc91e289f0>{number = 3name = 广州售票窗口} Will Exit 
  16.     2016-04-06 19:25:38.070 MutiThread[4705:1371665] <NSThread: 0x7fdc91e24d60>{number = 2name = 北京售票窗口} Will Exit 

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

售票是一个典型的需要线程同步的场景,由于售票渠道有很多,而票的资源是有限的,当多个渠道在短时间内卖出大量的票的时候,如果没有同步机制来管理票的数量,将会导致票的总数和售出票数对应不上的错误。

  1. 我们在售票的过程中给票加上同步锁:同一时间内,只有一个线程能对票的数量进行操作,当操作完成之后,其他线程才能继续对票的数量进行操作。 
  2.      - (void)saleTicket { 
  3.          while (1) { 
  4.              @synchronized(self) { 
  5.                  //如果还有票,继续售卖 
  6.                  if (_ticketCount > 0) { 
  7.                      _ticketCount --; 
  8.                      NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]); 
  9.                      [NSThread sleepForTimeInterval:0.2]; 
  10.                  } 
  11.                  //如果已卖完,关闭售票窗口 
  12.                  else { 
  13.                      break; 
  14.                  } 
  15.              } 
  16.          } 
  17.      } 
  1. 运行结果: 
  2.     2016-04-06 19:31:27.913 MutiThread[4718:1406865] 剩余票数:11 窗口:北京售票窗口 
  3.     2016-04-06 19:31:28.115 MutiThread[4718:1406866] 剩余票数:10 窗口:广州售票窗口 
  4.     2016-04-06 19:31:28.317 MutiThread[4718:1406865] 剩余票数:9 窗口:北京售票窗口 
  5.     2016-04-06 19:31:28.522 MutiThread[4718:1406866] 剩余票数:8 窗口:广州售票窗口 
  6.     2016-04-06 19:31:28.728 MutiThread[4718:1406865] 剩余票数:7 窗口:北京售票窗口 
  7.     2016-04-06 19:31:28.929 MutiThread[4718:1406866] 剩余票数:6 窗口:广州售票窗口 
  8.     2016-04-06 19:31:29.134 MutiThread[4718:1406865] 剩余票数:5 窗口:北京售票窗口 
  9.     2016-04-06 19:31:29.339 MutiThread[4718:1406866] 剩余票数:4 窗口:广州售票窗口 
  10.     2016-04-06 19:31:29.545 MutiThread[4718:1406865] 剩余票数:3 窗口:北京售票窗口 
  11.     2016-04-06 19:31:29.751 MutiThread[4718:1406866] 剩余票数:2 窗口:广州售票窗口 
  12.     2016-04-06 19:31:29.952 MutiThread[4718:1406865] 剩余票数:1 窗口:北京售票窗口 
  13.     2016-04-06 19:31:30.158 MutiThread[4718:1406866] 剩余票数:0 窗口:广州售票窗口 
  14.     2016-04-06 19:31:30.363 MutiThread[4718:1406866] <NSThread: 0x7ff0c1637320>{number = 3name = 广州售票窗口} Will Exit 
  15.     2016-04-06 19:31:30.363 MutiThread[4718:1406865] <NSThread: 0x7ff0c1420cb0>{number = 2name = 北京售票窗口} Will Exit 

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

  线程的持续运行和退出

我们注意到,线程启动后,执行saleTicket完毕后就马上退出了,怎样能让线程一直运行呢(窗口一直开放,可以随时指派其卖演唱会的门票的任务),答案就是给线程加上runLoop

  1. ``` 
  2.      先监听线程退出的通知,以便知道线程什么时候退出 
  3.      [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(threadExitNotice) name:NSThreadWillExitNotification object:nil]; 
  4.  ``` 
  1. //设置演唱会的门票数量 
  2.      _ticketCount = 50
  1. 新建两个子线程(代表两个窗口同时销售门票) 
  2.      NSThread * window1 = [[NSThread alloc]initWithTarget:self selector:@selector(thread1) object:nil]; 
  3.      [window1 start]; 
  4.  
  5.      NSThread * window2 = [[NSThread alloc]initWithTarget:self selector:@selector(thread2) object:nil]; 
  6.      [window2 start]; 
  1. 接着我们给线程创建一个runLoop 
  2.      - (void)thread1 { 
  3.          [NSThread currentThread].name = @"北京售票窗口"; 
  4.          NSRunLoop * runLoop1 = [NSRunLoop currentRunLoop]; 
  5.          [runLoop1 runUntilDate:[NSDate date]]; //一直运行 
  6.      } 
  7.  
  8.      - (void)thread2 { 
  9.          [NSThread currentThread].name = @"广州售票窗口"; 
  10.          NSRunLoop * runLoop2 = [NSRunLoop currentRunLoop]; 
  11.          [runLoop2 runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10.0]]; //自定义运行时间 
  12.      } 
  1. 然后就可以指派任务给线程了,这里我们让两个线程都执行相同的任务(售票) 
  2.      [self performSelector:@selector(saleTicket) onThread:window1 withObject:nil waitUntilDone:NO]; 
  3.      [self performSelector:@selector(saleTicket) onThread:window2 withObject:nil waitUntilDone:NO]; 
  1. 运行结果: 
  2.      2016-04-06 19:43:22.585 MutiThread[4762:1478200] 剩余票数:11 窗口:北京售票窗口 
  3.      2016-04-06 19:43:22.788 MutiThread[4762:1478201] 剩余票数:10 窗口:广州售票窗口 
  4.      2016-04-06 19:43:22.993 MutiThread[4762:1478200] 剩余票数:9 窗口:北京售票窗口 
  5.      2016-04-06 19:43:23.198 MutiThread[4762:1478201] 剩余票数:8 窗口:广州售票窗口 
  6.      2016-04-06 19:43:23.404 MutiThread[4762:1478200] 剩余票数:7 窗口:北京售票窗口 
  7.      2016-04-06 19:43:23.609 MutiThread[4762:1478201] 剩余票数:6 窗口:广州售票窗口 
  8.      2016-04-06 19:43:23.810 MutiThread[4762:1478200] 剩余票数:5 窗口:北京售票窗口 
  9.      2016-04-06 19:43:24.011 MutiThread[4762:1478201] 剩余票数:4 窗口:广州售票窗口 
  10.      2016-04-06 19:43:24.216 MutiThread[4762:1478200] 剩余票数:3 窗口:北京售票窗口 
  11.      2016-04-06 19:43:24.422 MutiThread[4762:1478201] 剩余票数:2 窗口:广州售票窗口 
  12.      2016-04-06 19:43:24.628 MutiThread[4762:1478200] 剩余票数:1 窗口:北京售票窗口 
  13.      2016-04-06 19:43:24.833 MutiThread[4762:1478201] 剩余票数:0 窗口:广州售票窗口 
  14.      2016-04-06 19:43:25.039 MutiThread[4762:1478201] <NSThread: 0x7fe0d3c24360>{number = 3name = 广州售票窗口} Will Exit 

可以看到,当票卖完后,两个线程并没有退出,仍在继续运行,当到达指定时间后,线程2退出了,如果需要让线程1退出,需要我们手动管理。

比如我们让线程完成任务(售票)后自行退出,可以这样操作

  1. - (void)saleTicket { 
  2.         while (1) { 
  3.             @synchronized(self) { 
  4.             //如果还有票,继续售卖 
  5.                 if (_ticketCount > 0) { 
  6.                     _ticketCount --; 
  7.                     NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", _ticketCount, [NSThread currentThread].name]); 
  8.                     [NSThread sleepForTimeInterval:0.2]; 
  9.                 } 
  10.                 //如果已卖完,关闭售票窗口 
  11.                 else { 
  12.                     if ([NSThread currentThread].isCancelled) { 
  13.                        break; 
  14.                     }else { 
  15.                        NSLog(@"售卖完毕"); 
  16.                        //给当前线程标记为取消状态 
  17.                        [[NSThread currentThread] cancel]; 
  18.                        //停止当前线程的runLoop 
  19.                        CFRunLoopStop(CFRunLoopGetCurrent()); 
  20.                     } 
  21.                 } 
  22.             } 
  23.         } 
  24.     } 
  1. 运行结果: 
  2.      2016-04-06 20:08:38.287 MutiThread[4927:1577193] 剩余票数:10 窗口:北京售票窗口 
  3.      2016-04-06 20:08:38.489 MutiThread[4927:1577194] 剩余票数:9 窗口:广州售票窗口 
  4.      2016-04-06 20:08:38.690 MutiThread[4927:1577193] 剩余票数:8 窗口:北京售票窗口 
  5.      2016-04-06 20:08:38.892 MutiThread[4927:1577194] 剩余票数:7 窗口:广州售票窗口 
  6.      2016-04-06 20:08:39.094 MutiThread[4927:1577193] 剩余票数:6 窗口:北京售票窗口 
  7.      2016-04-06 20:08:39.294 MutiThread[4927:1577194] 剩余票数:5 窗口:广州售票窗口 
  8.      2016-04-06 20:08:39.499 MutiThread[4927:1577193] 剩余票数:4 窗口:北京售票窗口 
  9.      2016-04-06 20:08:39.700 MutiThread[4927:1577194] 剩余票数:3 窗口:广州售票窗口 
  10.      2016-04-06 20:08:39.905 MutiThread[4927:1577193] 剩余票数:2 窗口:北京售票窗口 
  11.      2016-04-06 20:08:40.106 MutiThread[4927:1577194] 剩余票数:1 窗口:广州售票窗口 
  12.      2016-04-06 20:08:40.312 MutiThread[4927:1577193] 剩余票数:0 窗口:北京售票窗口 
  13.      2016-04-06 20:08:40.516 MutiThread[4927:1577194] 售卖完毕 
  14.      2016-04-06 20:08:40.516 MutiThread[4927:1577193] 售卖完毕 
  15.      2016-04-06 20:08:40.517 MutiThread[4927:1577193] <NSThread: 0x7fb719d54000>{number = 2name = 北京售票窗口} Will Exit 
  16.      2016-04-06 20:08:40.517 MutiThread[4927:1577194] <NSThread: 0x7fb719d552f0>{number = 3name = 广州售票窗口} Will Exit 

如果确定两个线程都是isCancelled状态,可以调用[NSThread exit]方法来终止线程。 

责任编辑:倪明 来源: 明仔Su的简书
相关推荐

2013-06-07 16:30:08

iOS多线程iOS开发NSThread

2013-08-21 16:17:09

iPhone多线程

2013-07-16 13:39:11

2013-07-16 10:12:14

iOS多线程多线程概念多线程入门

2015-07-22 09:39:38

IOS多线程同步

2015-07-22 09:51:51

iOS开发线程

2011-08-02 10:26:59

iOS 多线程 线程

2013-07-16 10:57:34

iOS多线程多线程概念多线程入门

2013-07-16 12:13:27

iOS多线程多线程概念GCD

2013-07-15 15:35:06

2021-12-14 08:28:08

Java多线程线程

2021-02-25 15:58:46

C++线程编程开发技术

2011-08-18 17:07:23

IOS开发多线程NSInvocatio

2021-07-19 07:55:24

多线程模型Redis

2015-07-29 09:22:25

IOS多线程

2013-07-16 11:38:46

iOS多线程多线程概念GCD

2013-03-27 10:32:53

iOS多线程原理runloop介绍GCD

2018-04-11 10:51:25

多线程进程主线程

2019-09-24 14:19:12

PythonC语言文章

2021-03-28 09:12:58

多线程死锁技术热点
点赞
收藏

51CTO技术栈公众号