#iOS多线程知识梳理
##线程进程基础概念
###进程
进程是指在系统中正在运行的一个应用程序每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内###线程
1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程,称为主线程)
一个进程(程序)的所有任务都在线程中执行>比较
1.线程是CPU调用(执行任务)的最小单位。
2.进程是CPU分配资源的最小单位。3.一个进程中至少要有一个线程。4.同一个进程内的线程共享进程的资源。 ###多线程概述1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务多线程技术可以提高程序的执行效率>原理
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
那么如果线程非常非常多,会发生什么情况?CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,同时每条线程被调度执行的频次也会会降低(线程的执行效率降低)。因此我们一般只开3-5条线程>优缺点
多线程的优点
能适当提高程序的执行效率能适当提高资源利用率(CPU、内存利用率)多线程的缺点创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间如果开启大量的线程,会降低程序的性能,线程越多,CPU在调度线程上的开销就越大。程序设计更加复杂:比如线程之间的通信、多线程的数据共享等问题>多线程应用
主线程的主要作用
显示\刷新UI界面处理UI事件(比如点击事件、滚动事件、拖拽事件等)主线程的使用注意别将比较耗时的操作放到主线程中耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验将耗时操作放在子线程中执行,提高程序的执行效率##iOS 开发中的几种线程知识点
###pthread(c)
POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32
>具体使用
```
//创建线程 pthread_t thread; /* 第一个参数pthread_t *restrict:线程对象 第二个参数const pthread_attr_t *restrict:线程属性 第三个参数void *(*)(void *) :指向函数的指针 第四个参数void *restrict:函数的参数 */ pthread_create(&thread, NULL,run ,NULL);```###NSThread
>相对 pthread 提供了面向对象的方法使用更加简单
```
// 获取当前线程 + (NSThread *)currentThread; // 创建启动线程 + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; // 判断是否是多线程 + (BOOL)isMultiThreaded; // 线程休眠 NSDate 休眠到什么时候 + (void)sleepUntilDate:(NSDate *)date; // 线程休眠时间 + (void)sleepForTimeInterval:(NSTimeInterval)ti; // 结束/退出当前线程 + (void)exit; // 获取当前线程优先级 + (double)threadPriority; // 设置线程优先级 默认为0.5 取值范围为0.0 - 1.0 // 1.0优先级最高 // 设置优先级 + (BOOL)setThreadPriority:(double)p; // 获取指定线程的优先级 - (double)threadPriority NS_AVAILABLE(10_6, 4_0); - (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);// 设置线程的名字
- (void)setName:(NSString *)n NS_AVAILABLE(10_5, 2_0); - (NSString *)name NS_AVAILABLE(10_5, 2_0);// 判断指定的线程是否是 主线程
- (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);- (id)init NS_AVAILABLE(10_5, 2_0); // designated initializer
// 创建线程 - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument NS_AVAILABLE(10_5, 2_0); // 指定线程是否在执行 - (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0); // 线程是否完成 - (BOOL)isFinished NS_AVAILABLE(10_5, 2_0); // 线程是否被取消 (是否给当前线程发过取消信号) - (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0); // 发送线程取消信号的 最终线程是否结束 由 线程本身决定 - (void)cancel NS_AVAILABLE(10_5, 2_0); // 启动线程 - (void)start NS_AVAILABLE(10_5, 2_0);// 线程主函数 在线程中执行的函数 都要在-main函数中调用,自定义线程中重写-main方法
- (void)main NS_AVAILABLE(10_5, 2_0); // thread body meth```
##GCD
###GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码>核心:任务和队列
####任务
任务:执行什么操作,任务有两种执行方式: 同步函数 和 异步函数,他们之间的区别是
同步:只能在当前线程中执行任务,不具备开启新线程的能力,任务立刻马上执行,会阻塞当前线程并等待 Block中的任务执行完毕,然后当前线程才会继续往下运行
异步:可以在新的线程中执行任务,具备开启新线程的能力,但不一定会开新线程,当前线程会直接往下执行,不会阻塞当前线程
* 异步操作:dispatch_async
* 同步操作:dispatch_sync
``` //async: asynchronous 将任务异步的追加到队列中dispatch_async(dispatch_get_global_queue(0,0), ^{
NSLog(@"async");
});
//sync: synchronous将任务同步的追加到队列中(等队列中的任务执行完,再将任务追加到队列)
//是同步追加,不是任务同步执行,在串行队列中,任务才同步执行
dispatch_sync(dispatch_get_global_queue(0,0), ^{
NSLog(@"sync");
});
```
#### 队列
队列:用来存放任务,分为串行队列 和 并行队列串行队列(Serial Dispatch Queue)
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)并发队列(Concurrent Dispatch Queue)
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效>队列代码
```
dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);```* Serial Dispatch Queue --- 等待现在正在执行的任务处理结束(串行)
* Concurrent Dispatch Queue --- 不等待现在正在执行的任务处理结束(并行、并发)
>并发队列
```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_CONCURRENT);```>串行队列
```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);```###优先级
```
/** 第一个参数:优先级 也可直接填后面的数字 #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台 第二个参数: 预留参数 0 */ dispatch_queue_t quque1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);```###任务和队列的组合
1. 任务:同步函数 异步函数
2. 队列:串行 并行
* 异步函数+并发队列:会开启新的线程,并发执行
```
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]);});``` * 异步函数+串行队列:会开启一条线程,任务串行执行```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL);dispatch_async(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]);});``` * 同步函数+并发队列:不会开线程,任务串行执行```
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]);});```* 同步函数+串行队列:不会开线程,任务串行执行
```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]);});```* 异步函数+主队列:不会开线程,任务串行执行
```
dispatch_queue_t queue = dispatch_queue_create("com.xxcc", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]);});``` *同步函数+主队列:死锁```
//1.获得主队列dispatch_queue_t queue = dispatch_get_main_queue();//2.同步函数dispatch_sync(queue, ^{ NSLog(@"---download1---%@",[NSThread currentThread]);});```#### 同步异步函数区别
* 同步函数:立刻马上执行,会阻塞当前线程
* 异步函数:当前线程会直接往下执行,不会阻塞当前线程
##GCD 常用函数用法####1. dispatch_after
>延迟执行
```
/* 第一个参数:延迟时间 第二个参数:要执行的代码 如果想让延迟的代码在子线程中执行,也可以更改在哪个队列中执行 dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0) */ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"---%@",[NSThread currentThread]); });```####2. dispatch_once
>一次性代码
```
-(void)once { //整个程序运行过程中只会执行一次 //onceToken用来记录该部分的代码是否被执行过 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"-----"); }); } //创建一个单例方法 + (ShareOnce *)shanreInstence{static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[ShareOncealloc] init];
NSLog(@"只执行1次");
});
returninstance;
}
``` ####3. dispatch_group_async & dispatch_group_notify>组队列
```
// 创建队列组 dispatch_group_t group = dispatch_group_create(); // 创建并行队列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 执行队列组任务 dispatch_group_async(group, queue, ^{ }); //队列组中的任务执行完毕之后,执行该函数 dispatch_group_notify(group, queue, ^{ });``` #####例子```
//并行队列执行任务,在并行队列中加入多个串行队列//disptch_group
dispatch_group_t group =dispatch_group_create();
dispatch_queue_t queue =dispatch_get_global_queue(0,0);
dispatch_group_async(group, queue, ^{
NSLog(@"task 01");
});
dispatch_group_async(group, queue, ^{
NSLog(@"task 02");
});
dispatch_group_async(group, queue, ^{
sleep(6);//等待6秒执行任务3 目的:测试等待时间的dispatch_group_wait这个方法
NSLog(@"task 03");
});
dispatch_group_async(group, queue, ^{
sleep(2);
NSLog(@"task 04");
});
//监视函数
//监视队列中得任务结束,执行block中得任务
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
//等待时间
dispatch_time_t time =dispatch_time(DISPATCH_TIME_NOW,5ull*NSEC_PER_SEC);
//dispatch_group_wait 指定时间后,看你一眼queue是否执行完毕
//如果执行完返回0
//没有执行完,返回非0值
long result = dispatch_group_wait(group, time);
if (result == 0) {
NSLog(@"finish");
}else{
NSLog(@"not finish");
}}``` ####4. dispatch_barrier_async>栅栏函数
栅栏函数可以控制任务执行的顺序,栅栏函数之前的执行完毕之后,执行栅栏函数,然后在执行栅栏函数之后的
####5. dispatch_apply
>快速迭代
``` /* 第一个参数:迭代的次数 第二个参数:在哪个队列中执行 第三个参数:block要执行的任务 */dispatch_apply(10, queue, ^(size_t index) { });``` ####6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait>信号量机制
```
//使用dispatch_queue_t queue =dispatch_get_global_queue(0,0);
dispatch_semaphore_t dsema =dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArrayarray];
for (int i =0; i < 1000; i++) {
dispatch_async(queue, ^{ dispatch_semaphore_wait(dsema,DISPATCH_TIME_FOREVER); [arrayaddObject:[NSNumbernumberWithInt:i]]; dispatch_semaphore_signal(dsema);});
}
NSLog(@"%@", array);
``` ##NSOperation NSOperation 是苹果公司对 GCD 的封装,完全面向对象,并比GCD多了一些更简单实用的功能,所以使用起来更加方便易于理解。NSOperation 和NSOperationQueue 分别对应 GCD 的 任务 和 队列。NSOperation和NSOperationQueue实现多线程的具体步骤
1.将需要执行的操作封装到一个NSOperation对象中
2.将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来,并将取出的NSOperation封装的操作放到一条新线程中执行###NSOperation
>NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
####使用NSOperation子类的方式有3种
#####1.NSInvocationOperation
```
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download) object:nil];//启动操作[op start];```#####2.NSBlockOperation 常用的一种
```
//1.封装操作NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ //要执行的操作,在主线程中执行 NSLog(@"1------%@",[NSThread currentThread]); }];//2.追加操作,追加的操作在子线程中执行,可以追加多条操作[op addExecutionBlock:^{ NSLog(@"---download2--%@",[NSThread currentThread]); }];[op start];```#####3.自定义类继承NSOperation,实现内部方法,复用性强
```
// 重写自定义类的main方法实现封装操作-(void)main{ // 要执行的操作} // 实例化一个自定义对象,并执行操作CustomOperation *op = [[CustomOperation alloc]init];[op start];```##NSOperationQueue队列的使用
###两种队列
主队列:通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行
非主队列:直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行
>作用
NSOperation可以调用start方法来执行任务,但默认是同步执行的
如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作####两者结合的方法使用 NSOperation和NSOperationQueue结合使用创建多线程
```
注:这里使用NSBlockOperation示例,其他两种方法一样 // 1. 创建非主队列 同时具备并发和串行的功能,默认是并发队列 NSOperationQueue *queue =[[NSOperationQueue alloc]init]; //NSBlockOperation 不论封装操作还是追加操作都是异步并发执行 // 2. 封装操作 NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"download1 -- %@",[NSThread currentThread]); }]; // 3. 将封装操作加入主队列 // 也可以不获取封装操作对象 直接添加操作到队列中 //[queue addOperationWithBlock:^{ // 操作 //}]; [queue addOperation:op1];```
###NSOperation和NSOperationQueue的重要属性和方法
* NSOperation的依赖
* NSOperation监听操作
* NSOperationQueue
* maxConcurrentOperationCount 并发数
* suspended 是否暂停
* cancelAllOperations 取消所有任务,不再执行,不可逆
>关键点:暂停和取消只能暂停或取消处于等待状态的任务,不能暂停或取消正在执行中的任务,必须等正在执行的任务执行完毕之后才会暂停,如果想要暂停或者取消正在执行的任务,可以在每个任务之间即每当执行完一段耗时操作之后,判断是否任务是否被取消或者暂停。如果想要精确的控制,则需要将判断代码放在任务之中,但是不建议这么做,频繁的判断会消耗太多时间
##一个例子
```
#import "ViewController.h"@interface ViewController ()@property (weak, nonatomic) IBOutlet UIImageView *imageView;@property(nonatomic,strong)UIImage *image1;@property(nonatomic,strong)UIImage *image2;@end@implementation ViewController-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{ // 创建非住队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init]; // 下载第一张图片 NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; self.image1 = [UIImage imageWithData:data]; }]; // 下载第二张图片 NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{ NSURL *url = [NSURL URLWithString:@"http://img2.3lian.com/2014/c7/12/d/77.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; self.image2 = [UIImage imageWithData:data]; }]; // 合成操作 NSBlockOperation *combie = [NSBlockOperation blockOperationWithBlock:^{ // 开启图形上下文 UIGraphicsBeginImageContext(CGSizeMake(375, 667)); // 绘制图片1 [self.image1 drawInRect:CGRectMake(0, 0, 375, 333)]; // 绘制图片2 [self.image2 drawInRect:CGRectMake(0, 334, 375, 333)]; // 获取合成图片 UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 关闭图形上下文 UIGraphicsEndImageContext(); // 回到主线程刷新UI [[NSOperationQueue mainQueue]addOperationWithBlock:^{ self.imageView.image = image; }]; }]; // 添加依赖,合成图片需要等图片1,图片2都下载完毕之后合成 [combie addDependency:download1]; [combie addDependency:download2]; // 添加操作到队列 [queue addOperation:download1]; [queue addOperation:download2]; [queue addOperation:combie];}@end```##多线程共享读写资源隐患问题
>容易死锁
```
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"1========%@",[NSThread currentThread]); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2========%@",[NSThread currentThread]); }); NSLog(@"3========%@",[NSThread currentThread]);}ThreadDemo[5615:874679] 1========{number = 1, name = main}
```
>原因:任务A等待任务B完成才能继续执行,但作为串行队列的主队列又不能让任务B在任务A未完成之前开始执行,所以任务A等着任务B完成,任务B等着任务A完成>###解决方案:
1.普通执行
2.同步队列3.异步队列 >多线程安全隐患的原因:1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同一个文件。那么当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。> 可以使用互斥锁解决
几种加锁方法
* synchronized
@synchronized(锁对象) {
// 需要锁定的代码 }* NSLock