多线程
2017-01-12 本文已影响26人
CoderLNHui
# 多线程使用
一、NSThread
1、创建的3种基本使用方式
//第一种创建线程的方式:alloc init.
//特点:需要手动开启线程,可以拿到线程对象进行详细设置
//创建线程
/*
第一个参数:目标对象
第二个参数:选择器,线程启动要调用哪个方法
第三个参数:前面方法要接收的参数(最多只能接收一个参数,没有则传nil)
*/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"person"];
//启动线程
[thread start];
//设置属性
thread.name = @"线程";
//设置优先级 取值范围 0.0 ~ 1.0 之间 最高是1.0 默认优先级是0.5
thread.threadPriority = 1.0;
//第二种创建线程的方式:分离出一条子线程
//特点:自动启动线程,无法对线程进行更详细的设置
/*
第一个参数:线程启动调用的方法
第二个参数:目标对象
第三个参数:传递给调用方法的参数
*/
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"我是分离出来的子线程"];
//第三种创建线程的方式:后台线程
//特点:自动启动线程,无法进行更详细设置
[self performSelectorInBackground:@selector(run:) withObject:@"后台线程"];
2、线程之间通信方式
- (void)viewDidLoad
{ //开启一条子线程来下载图片
[NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
}
-(void)downloadImage
{
//1.确定要下载网络图片的url地址,一个url唯一对应着网络上的一个资源
NSURL *url = [NSURL URLWithString:@"下载图片的urlStr"];
//2.根据url地址下载图片数据到本地(二进制数据
NSData *data = [NSData dataWithContentsOfURL:url];
//3.把下载到本地的二进制数据转换成图片
UIImage *image = [UIImage imageWithData:data];
//4.回到主线程刷新UI
//4.1 第一种方式
// [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
//4.2 第二种方式
// [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
//4.3 第三种方式
[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
二、GCD
1、核心概念:任务(执行什么操作)和队列(用来存放任务)
任务
同步函数dispatch_sync
- 只能在当前线程中执行任务,不具备开启新线程的能力
queue:队列
block:任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
异步函数dispatch_async
- 可以在新的线程中执行任务,具备开启新线程的能力
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
同步和异步主要影响:能不能开启新的线程
队列
并发队列(只有在异步(dispatch_async)
函数下才有效)
- 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
并发队列创建方式
- 第一种使用
dispatch_queue_create
函数创建队列
dispatch_queue_t
dispatch_queue_create(const char *label, // 队列名称
dispatch_queue_attr_t attr); // 队列的类型
- 第二种创建并发队列
/*
第一个参数:C语言的字符串,标签
第二个参数:队列的类型
DISPATCH_QUEUE_CONCURRENT:并发
DISPATCH_QUEUE_SERIAL:串行
*/
dispatch_queue_t queue = dispatch_queue_create("biaoqian", DISPATCH_QUEUE_CONCURRENT);
获得全局并发队列dispatch_get_global_queue
/*
第一个参数:队列优先级
#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 queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
串行队列
- 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
获得串行队列方式
- 第一种方式使用
dispatch_queue_create
函数创建串行队列
// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("com.queue", NULL);
- 第二种方式:使用
dispatch_get_main_queue()
获得主队列(主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列
放在主队列中的任务,都会放到主线程中执行
dispatch_queue_t queue = dispatch_get_main_queue();
并发和串行主要影响:任务的执行方式
2、任务和队列搭配使用
-
异步函数+并发队列: 会开启多条线程,队列中的任务是并发执行
-
异步函数+串行队列: 会开线程,开一条线程,队列中的任务是串行执行的
-
同步函数+并发队列: 不会开线程,任务是串行执行的
-
同步函数+串行队列: 不会开线程,任务是串行执行的
-
异步函数+主队列: 所有任务都在主线程中执行,不会开线程
-
同步函数+主队列: 死锁
造成死锁的原因:
在主线程中调用方法-(void)syncMain(内部用同步函数+主队列)
,由于同步函数特点和主队列是在主线程中执行的,此时主线程正在调用该方法,就会造成死锁;如果该方法在子线程中执行,主线程并没有使用忙于调用该方法,那么所有的任务在主线程中执行,就不会造成死锁同步函数:立刻马上执行,如果我没有执行完毕,那么后面的也别想执行
异步函数:如果我没有执行完毕,那么后面的也可以执行
3、GCD线程间的通信
dispatch_async(dispatch_get_main_queue(), ^{}
//1.创建子线程下载图片
//DISPATCH_QUEUE_PRIORITY_DEFAULT 0
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//1.1 确定url
NSURL *url = [NSURL URLWithString:@"UrlStr"];
//1.2 下载二进制数据到本地
NSData *imageData = [NSData dataWithContentsOfURL:url];
//1.3 转换图片
UIImage *image = [UIImage imageWithData:imageData];
//更新UI,dispatch_sync(dispatch_get_main_queue(),并不会造成死锁,在子线程中调用
// dispatch_async(dispatch_get_main_queue(), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
4、GCD其他常用函数
a、延迟执行三种方式
performSelector
1. 延迟执行的第一种方法
[self performSelector:@selector(task) withObject:nil afterDelay:2.0];
NSTimer scheduledTime...
2.延迟执行的第二种方法
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
dispatch_after
3.GCD
// dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
/*
第一个参数:DISPATCH_TIME_NOW 从现在开始计算时间
第二个参数:延迟的时间 2.0 GCD时间单位:纳秒
第三个参数:队列
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
NSLog(@"GCD----%@",[NSThread currentThread]);
});
b、一次性代码(不能放在懒加载中的,应用场景:单例模式)
dispatch_once
-(void)once
{
//整个程序运行过程中只会执行一次
//onceToken用来记录该部分的代码是否被执行过
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"---once----");
});
}
c、快速迭代(开子线程和主线程一起完成遍历任务,任务执行时是并发的)
dispatch_apply
/*
第一个参数:遍历的次数
第二个参数:队列(并发队列)
第三个参数:index 索引
*/
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zd---%@",index,[NSThread currentThread]);
});
#### d、栅栏函数(不能使用全局并发队列)
##### dispatch_barrier_async
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
//栅栏函数
dispatch_barrier_async(queue, ^{
NSLog(@"+++++++++++++++++++++++++++++");
});
e、队列组
dispatch_group_create
dispatch_group_async
dispatch_group_notify
主要函数:
//1.创建队列
dispatch_queue_t queue =dispatch_get_global_queue(0, 0);
//2.创建队列组
dispatch_group_t group = dispatch_group_create();
//3.异步函数
/*
1)封装任务
2)把任务添加到队列中
dispatch_async(queue, ^{
NSLog(@"1----%@",[NSThread currentThread]);
});
*/
/*
1)封装任务
2)把任务添加到队列中
3)会监听任务的执行情况,通知group
*/
dispatch_group_async(group, queue, ^{
});
//拦截通知,当队列组中所有的任务都执行完毕的时候回进入到下面的方法
dispatch_group_notify(group, queue, ^{
});
应用举例
-(void)group
{
/*
1.下载图片1 开子线程
2.下载图片2 开子线程
3.合成图片并显示图片 开子线程
*/
//-1.获得队列组
dispatch_group_t group = dispatch_group_create();
//0.获得并发队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 1.下载图片1 开子线程
dispatch_group_async(group, queue,^{
NSURL *url = [NSURL URLWithString:@"urlStrImage1"];
NSData *imageData = [NSData dataWithContentsOfURL:url];
self.image1 = [UIImage imageWithData:imageData];
});
// 2.下载图片2 开子线程
dispatch_group_async(group, queue,^{
//2.1 确定url
NSURL *url = [NSURL URLWithString:@"urlStrImage2"];
//2.2 下载二进制数据
NSData *imageData = [NSData dataWithContentsOfURL:url];
//2.3 转换图片
self.image2 = [UIImage imageWithData:imageData];
});
//3.合并图片
//dispatch_group_notify内部本身是异步的,不会阻塞
dispatch_group_notify(group,queue, ^{
//3.1 创建图形上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//3.2 画图1
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
self.image1 = nil;
//3.3 画图2
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
self.image2 = nil;
//3.4 根据上下文得到一张图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//3.5 关闭上下文
UIGraphicsEndImageContext();
//3.6 更新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"UI----%@",[NSThread currentThread]);
self.imageView.image = image;
});
});
}
-(void)group2
{
//1.创建队列
dispatch_queue_t queue =dispatch_get_global_queue(0, 0);
//2.创建队列组
dispatch_group_t group = dispatch_group_create();
//3.在该方法后面的异步任务会被纳入到队列组的监听范围,进入群组
//dispatch_group_enter|dispatch_group_leave 必须要配对使用
dispatch_group_enter(group);
dispatch_async(queue, ^{
//离开群组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
//离开群组
dispatch_group_leave(group);
});
//拦截通知
//问题?该方法是阻塞的吗? 内部本身是异步的
// dispatch_group_notify(group, queue, ^{
// NSLog(@"-------dispatch_group_notify-------");
// });
//等待.死等. 直到队列组中所有的任务都执行完毕之后才能执行
//阻塞的
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}
三、NSOperation
1、NSOperation和NSOperationQueue实现多线程的具体步骤
- 先将需要执行的操作封装到一个NSOperation对象中
- 然后将NSOperation对象添加到NSOperationQueue中
- 系统会自动将NSOperationQueue中的NSOperation取出来
- 将取出的NSOperation封装的操作放到一条新线程中执行
2、NSOperation子类的3种方式
a、NSInvocationOperation
//1.创建操作,封装任务
/*
第一个参数:目标对象 self
第二个参数:调用方法的名称
第三个参数:前面方法需要接受的参数 nil
*/
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
//2.启动|执行操作
[op1 start];// 默认是同步执行的
b、NSInvocationOperation
1.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
}];
/**
追加任务
注意:如果一个操作中的任务数量大于1,那么会开子线程并发执行任务
注意:不一定是子线程,有可能是主线程
*/
[op1 addExecutionBlock:^{
}];
2.启动
[op1 start];
c、自定义NSOption
- 自定义的NSOperation,通过重写内部的main方法实现封装操作
-(void)main
{
NSLog(@"--main--%@",[NSThread currentThread]);
}
//如何使用?
//1.实例化一个自定义操作对象
SHOperation *op = [[SHOperation alloc]init];
//2.执行操作
[op start];
3、NSOperationQueue 队列基本使用
NSOperation中的两种队列
- 主队列:
- [NSOperationQueue mainQueue] 和GCD中的主队列一样,串行队列
- 非主队列:
- [[NSOperationQueue alloc]init] 非常特殊(同时具备并发和串行的功能),默认情况下,非主队列是并发队列
//1.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
//追加任务
[op1 addExecutionBlock:^{
NSLog(@"4----%@",[NSThread currentThread]);
}];
//2.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//3.添加操作到队列中,系统会自动异步的执行NSOperation中的操作
[queue addOperation:op1]; //内部已经调用了[op1 start]
/*
简便方法
1)创建操作,2)添加操作到队列中
*/
[queue addOperationWithBlock:^{
NSLog(@"7----%@",[NSThread currentThread]);
}];
4、NSOperation其他用法
a、设置最大并发数
//1.创建队列
//默认是并发队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//2.设置最大并发数量 maxConcurrentOperationCount
//同一时间最多有多少个任务可以执行
//串行执行任务!=只开一条线程 (线程同步)
queue.maxConcurrentOperationCount = 1;
注意点:该属性需要在任务添加到队列中之前进行设置
该属性控制队列是串行执行还是并发执行
maxConcurrentOperationCount >1 那么就是并发队列
maxConcurrentOperationCount == 1 那就是串行队列
maxConcurrentOperationCount == 0 不会执行任务
maxConcurrentOperationCount == -1 特殊意义 最大值 表示不受限制
b、暂停和恢复suspended
- 队列中的任务也是有状态的:已经执行完毕的 | 正在执行 | 排队等待状态
- suspended设置为YES表示暂停,suspended设置为NO表示恢复
- 暂停表示不继续执行队列中的下一个任务,暂停操作是可以恢复的
- 不能暂停当前正在处于执行状态的任务
if (self.queue.isSuspended) {
//恢复
self.queue.suspended = NO;
}else
{
//暂停
self.queue.suspended = YES;
}
c、取消
- 取消,不可以恢复
- 取消之后,当前正在执行的操作的下一个操作将不再执行,而且永远都不在执行,就像后面的所有任务都从队列里面移除了一样
//该方法内部调用了所有操作的cancel方法
[self.queue cancelAllOperations];
d、操作监听(completionBlock)和依赖(addDependency)
//1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
//2.封装操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1---%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2---%@",[NSThread currentThread]);
}];
//操作监听
op2.completionBlock = ^{
};
//添加操作依赖
//注意点:不能循环依赖
//可以跨队列依赖
[op1 addDependency:op2];
//添加操作到队列
[queue addOperation:op1];
[queue2 addOperation:op2];
5、NSOpration实现线程间通信
//1.开子线程下载图片
//1.1 非主队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//1.2 封装操作
NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
//3.更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];
}];
//2.添加操作到队列
[queue addOperation:download];