iOS多线程总结——多线程相关概念及NSObject/NSThread的使用
Posted SSIrreplaceable
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS多线程总结——多线程相关概念及NSObject/NSThread的使用相关的知识,希望对你有一定的参考价值。
一. 多线程的相关概念
1. 什么是进程?
在操作系统发展的早期,为了提高资源利用率,使程序在多道程序下能并发执行,并对并发执行的程序加以控制和描述,在操作系统中引入了进程的概念。多道程序技术最早用于多道批处理系统,系统内可以同时存在多道作业,但同一时刻,系统只处理一道作业,作业根据系统的调度算法执行,每一个作业又由若干个程序组成,每个程序都可以完成独立的任务,且一个作业里面的程序是按顺序先后执行的,一个正在运行的程序称作一个进程,一个作业相当于系统中的一条执行任务的路径。在现代操作系统中,一个进程是指操作系统中正在运行的一个应用程序,是系统进行资源分配和调度的基本单位,每一个进程之间都是独立的,每一个进程均运行在其专用且受保护的内存空间内。
2. 什么是线程?
在60年代操作系统中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程;二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大。因此在80年代,即通用计算机系统发展的年代,出现了能独立运行的基本单位——线程(Threads)。在现代操作系统中,一个进程想要执行任务,必须得有线程,进程的所有任务都是在线程中执行,一个线程中任务的执行是串行的(即是按顺序的),一个线程要执行多个任务,只能一个一个地按顺序执行这些任务(指令代码),一个线程相当于进程中的一条执行任务的路径;一个进程可以有多条线程,但不能没有线程,至少要有一条线程(主线程)。线程是进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。
3. 什么是多线程?
在60年代操作系统,进程是系统拥有资源和独立运行的基本单位,它的创建、撤消与切换存在较大的时空开销,在80年代后的操作系统,为提高提高程序的执行效率,降低时空开销,引进线程的概念。多进程技术也是在这个时候出现的,具有多线程能力的计算机系统能够在同一时间执行多条线程。线程是系统独立调度和分派CPU的基本单位,在单处理机计算机系统中,多个线程是并发执行的,即系统中的多个线程根据线程调度算法快速地调度执行,如果调度的时间足够快,就造成多个线程并发执行的假象,但在同一时间,只有一条线程在执行;在有多个处理机或者有多核心处理器的计算机系统中,如果线程的数目小于处理机的数目(或小于处理器的核心数),多个线程一般是并行的(以线程调度算法而定),反之,线程一部分是并行一部分并发执行。
4. 多线程的优缺点
(1). 优点
能适当提高程序的执行效率
能适当提高资源利用率(CPU、内存利用率)
(2). 缺点
创建线程是有开销的,ios下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用 -setStackSize: 方法设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间,如果开启大量的线程,每条线程被调度执行的频次会降低(线程的执行效率降低),会降低程序的性能,且线程越多CPU在调度线程上的开销就越大,程序设计也更加复杂(比如线程之间的通信、多线程的数据共享更复杂)。
结论:线程不是开启越多越好,而是合适就好,一般一个程序开3条左右比较合理。
5. 什么是主线程?
在有多线程技术的操作系统中,当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程(Main Thread),在iOS中称为“UI线程”。在iOS中主线程的主要作用是显示\\刷新UI界面,处理UI事件(比如点击事件、滚动事件、拖拽事件等)。一般开发中不把耗时的操作放到主线程,因为耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验。主线程关系着整个应用程序的开始和结束,一般主线程终止了,进程也就随之终止。
6. 什么是子线程?
一个进程中,如果有多条线程,处了主线程外,都称为子线程(也叫后台线程或非主线程)。在单核CPU下,子线程和主线程会并发执行,在多核CPU下,可能会并行执行。子线程和主线程的执行没有先后,会根据系统的算法调度执行。iOS中子线程的作用一般用来处理一些耗时操作,或者可以通过多条子线程进行快速遍历。
注意:子线程在创建时只是申请了内存程序空间,但还并没有真正分配给二级线程,只有当子线程执行代码需要空间时才会真正分配。
7. iOS中多线程的实现方案
二. 多线程的实现
这里主要介绍:NSObject\\NSThread\\GCD\\NSOperation,因为在iOS中pthread几乎不用,所以这里不进行介绍。
(一). NSObject
NSObject也可以创建多线程,因为Apple给NSObject实现了一个关于线程的分类 NSObject (NSThreadPerformAdditions)
@interface NSObject (NSThreadPerformAdditions)
// 用于线程通信,子线程传到主线程
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
// 用于线程通信,子线程传到主线程,默认的运行时模式:kCFRunLoopCommonModes
- (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 NS_AVAILABLE(10_5, 2_0);
// 用于线程之间的通信
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
// 隐式创建线程,默认的运行时模式:kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
@end
(二). NSThread
1. 线程的创建与启动
// 创建线程
- (void)createThread
// 创建线程,NSThread的创建方法
// 一个NSThread对象就是一个线程
SJMNSThread *thread = [[SJMNSThread alloc] initWithTarget:self selector:@selector(run:) object:@"sjm"];
// 设置线程的名字
thread.name = @"线程";
NSLog(@"%@",thread);
// 启动线程
// 线程一启动,就会在线程thread中执行self的run方法
[thread start];
// 线程任务
- (void)run:(NSString *)string
// 获取当前线程
NSThread *thread = [NSThread currentThread];
thread.name = @"子线程";
NSLog(@"%@-----%@",string,[NSThread currentThread]);
2. 其他创建线程的方法
// 创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
// 隐式创建并启动线程,此方法来自NSObject的分类 NSObject (NSThreadPerformAdditions)
[self performSelectorInBackground:@selector(run) withObject:nil];
上述2种创建线程方式的优缺点
- 优点:简单快捷
- 缺点:无法在线程创建时对线程进行更详细的设置,只能在run方法中进行设置,即获取当前线程对象进行设置。
3. 退出线程
退出线程的方式有cancle和exit方法
(1). 两者的区别:
- 使用cancle方法退出线程,线程不会马上退出,会在某些线程堵塞的情况下退出,但也不一定,它的退出是未知的。
- 使用exit方法退出线程,线程会强制马上退出,exit方法没有给线程清理自己并释放资源的时间,可能会造成资源泄露。
(2). 退出线程的做法:
- 调用cancel方法,并把线程变量赋值为nil。
- 也可以cancle和exit结合使用,使用cancle进行标记,使用exit退出。
4. NSThread的接口
@interface NSThread : NSObject
@private
id _private;
uint8_t _bytes[44];
// 获取当前线程
+ (NSThread *)currentThread;
// 创建新线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
// 是否是多线程
+ (BOOL)isMultiThreaded;
/**
* 每个线程都维护了一个“键-值”的字典,它可以在线程里面的任何地方被访问,
* 可以使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。
* 比如,可以使用它来存储在整个线程过程中RunLoop里面多次迭代的状态信息。
* 使用:通过threadDictionary方法获取一个NSMutableDictionary对象,然后添加需要的字段和数据
*/
@property (readonly, retain) NSMutableDictionary *threadDictionary;
// 设置线程睡眠/堵塞
+ (void)sleepUntilDate:(NSDate *)date;
// 设置线程睡眠/堵塞
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
// 结束/退出进程
+ (void)exit;
// 获取线程的优先级
+ (double)threadPriority;
// 设置线程优先级,取值范围0.0~1.0
+ (BOOL)setThreadPriority:(double)p;
// 线程优先级,iOS8以后推荐使用qualityOfService属性,通过量化的优先级枚举值来设置
@property double threadPriority;
/** 线程优先级
qualityOfService的枚举值如下:
NSQualityOfServiceUserInteractive:最高优先级,用于用户交互事件
NSQualityOfServiceUserInitiated:次高优先级,用于用户需要马上执行的事件
NSQualityOfServiceDefault:默认优先级,主线程和没有设置优先级的线程都默认为这个优先级
NSQualityOfServiceUtility:普通优先级,用于普通任务
NSQualityOfServiceBackground:最低优先级,用于不重要的任务
*/
@property NSQualityOfService qualityOfService;
// 返回当前线程在栈中所占的地址所组成的数组
+ (NSArray<NSNumber *> *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0);
// 返回栈空间的符号表
+ (NSArray<NSString *> *)callStackSymbols NS_AVAILABLE(10_6, 4_0);
// 线程名称
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
// 栈的所占空间大小
@property NSUInteger stackSize NS_AVAILABLE(10_5, 2_0);
// 是否是主线程
@property (readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);
// 判断当前线程是否是主线程
+ (BOOL)isMainThread NS_AVAILABLE(10_5, 2_0); // reports whether current thread is main
// 获取主线程
+ (NSThread *)mainThread NS_AVAILABLE(10_5, 2_0);
// 初始化线程
- (instancetype)init NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER;
// 初始化线程
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);
// 是否正在执行
@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);
// 是否执行完毕
@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);
// 是否已经取消/中止
@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);
// 取消线程,不能再开始
- (void)cancel NS_AVAILABLE(10_5, 2_0);
// 开始线程
- (void)start NS_AVAILABLE(10_5, 2_0);
/** main是线程入口
* - (void)main的使用:
* 1. 一般创建线程会子类化NSThread,重写main方法,把关于线程执行的方法都写在里面,这样可以在任何需要这个线程方法的地方直接使用。
* 2. 把线程执行的方法写在main里,是因为线程的操作应该属于线程的本身,而不是每次使用都通过initWithTarget:selector:object:方法,且再一次实现某个方法。
* 3. 当重写了main方法后,同时使用initWithTarget:selector:object:方法初始化,调用某个方法执行任务,系统默认只执行main方法里面的任务。
* 4. 如果直接使用NSThread创建线程,线程内执行的方法都是在当前的类文件里面的。
*/
- (void)main NS_AVAILABLE(10_5, 2_0);
@end
5. 多线程的状态
计算机中的线程在内存中得可调度线程池中,每一条线程都有5个状态。
注意:一旦线程停止(死亡)了,就不能再次开启任务。
(1). 启动线程
// 进入就绪状态或直接进入运行状态。当线程任务执行完毕,自动进入死亡状态
- (void)start;
(2). 阻塞(暂停)线程
// 进入阻塞状态
// 堵塞到遥远未来,系统会自动杀死线程,[NSThread sleepUntilDate:[NSDate distantFuture]];
// 堵塞到遥远的过去,线程不会堵塞,[NSThread sleepUntilDate:[NSDate distantPast]];
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
(3). 强制停止线程
// 进入死亡状态
+ (void)exit;
6. 多线程的安全隐患
- 线程同步:多条线程在同一条线上执行(按顺序地执行任务)
- 互斥锁:就是使用了线程同步技术
(1). 资源共享 - 数据错乱
1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件,当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。
(2). 安全隐患解决 – 互斥锁
- 互斥锁使用格式
// 注意:锁定1份代码只用1把锁,用多把锁是无效的。
@synchronized(锁对象) // 需要锁定的代码
提示: “锁对象”也被称为一个“信号量”。当一个线程要执行这段代码时,它会检查其他的线程是否也在访问“锁对象”,如果没有线程在访问“锁对象”,这块代码会被执行;否则这段线程会被限制访问直到这个互斥锁解除为止。 互斥锁的优缺点:
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源互斥锁的使用前提:多条线程抢夺同一块资源
补充: iOS中有两种加锁方式,即NSLock与NSCondition,之后又通过@synchronized替代了NSLock复杂的书写方式,确保线程同步。
(3). 原子和非原子属性
OC在定义属性时有nonatomic和atomic两种选择
atomic:原子属性,为setter方法加锁(默认就是atomic)
nonatomic:非原子属性,不会为setter方法加锁nonatomic和atomic对比
atomic:线程安全,需要消耗大量的资源
nonatomic:非线程安全,适合内存小的移动设备iOS开发的建议
所有属性都声明为nonatomic,尽量避免多线程抢夺同一块资源,尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力。
7. 多线程的通信
(1). 什么叫做线程间通信
在1个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信
(2). 线程间通信的体现
- 1个线程传递数据给另1个线程
- 在1个线程中执行完特定任务后,转到另1个线程继续执行任务
(3). 线程间通信常用方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
(4). 以前常用线程通信
- NSPort
- NSMessagePort
- NSMachPort
8. 线程的通信实例
实例:点击按钮,添加网络图片
注意:在Xcoed7后,使用dataWithContentsOfURL加载网络数据为出错
App Transport Security has blocked a cleartext HTTP (http://) resource load since it is insecure. Temporary exceptions can be configured via your app's Info.plist file.
解决方法:是在Info.plist中添加参数
- 在 Info.plist 中添加 NSAppTransportSecurity,类型为 Dictionary 。
- 在 NSAppTransportSecurity 下添加 NSAllowsArbitraryLoads, 类型为 Boolean,值设为 YES。
实例界面(加载图片后):
具体代码:
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
- (IBAction)btnClick:(id)sender
// 创建线程
[NSThread detachNewThreadSelector:@selector(imageLoad) toTarget:self withObject:nil];
// 线程执行任务
- (void)imageLoad
// 从网络下载图片
NSURL *url = [NSURL URLWithString:@"http://img.51ztzj.com/upload/image/20140618/sj201406181009_279x419.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 线程通信,子线程把image传给主线程
// waitUntilDone:是否等主线程执行完setImage:后子线程才往下执行
// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
// [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
// setImage:是self.imageView属性image的setter方法
// [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];
NSLog(@"---%s---",__func__);
- (void)showImage:(UIImage *)image
_imageView.image = image;
NSLog(@"%s---%@",__func__,[NSThread currentThread]);
@end
以上是关于iOS多线程总结——多线程相关概念及NSObject/NSThread的使用的主要内容,如果未能解决你的问题,请参考以下文章
iOS多线程总结——NSOperation与NSOperationQueue的使用
C++11多线程第一篇:并发基本概念及实现,进程线程基本概念