多线程总结
2017-11-22 本文已影响0人
freemanIT
概念
-
进程: 1.一个具有一定独立功能的程序关于某个数据集合的一次运行活动。可以理解成一个运行中的应用程序。2.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内
-
线程: 1. 1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)。2. 一个进程(程序)的所有任务都在线程中执行
-
线程的串行: 如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务,也就是说,在同一时间内,1个线程只能执行1个任务
进程和线程的比较
1.线程是CPU调用(执行任务)的最小单位。
2.进程是CPU分配资源和调度的单位。
3.一个程序可以对应多个进程,一个进程中可以有多个线程,但至少要有一个线程。
4.同一个进程内的线程共享进程的资源。
- 多线程: 1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务, 多线程技术可以提高程序的执行效率
多线程原理:
1.同一时间,CPU只能处理1条线程,只有1条线程在工作(执行)
2.多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换)
3.如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
如果线程非常非常多:
1.CPU会在N多线程之间调度,消耗大量的CPU资源
2.每条线程被调度执行的频次会降低(线程的执行效率降低)
多线程的优缺点
- 多线程的优点
1.能适当提高程序的执行效率
2.能适当提高资源利用率(CPU、内存利用率)- 多线程的缺点
1.创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间
2.如果开启大量的线程,会降低程序的性能
3.线程越多,CPU在调度线程上的开销就越大
4.程序设计更加复杂:比如线程之间的通信、多线程的数据共享
多线程在iOS开发中的应用
- 一个iOS程序运行后,默认会开启1条线程,称为“主线程”或“UI线程”
- 主线程的主要作用: 显示\刷新UI界面, 处理UI事件(比如点击事件、滚动事件、拖拽事件等)
- 主线程的使用注意: 别将比较耗时的操作放到主线程中, 耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验, 将耗时操作放在子线程执行
//获得主线程
NSThread *mainThread = [NSThread mainThread];
//获得当前线程
NSThread *currentThread = [NSThread currentThread];
//判断主线程
BOOL isMainThread = [NSThread isMainThread];
iOS多线程实现方案
多线程.jpg- pthread
首先导入头文件
#import <pthread.h>
//1.创建线程对象
pthread_t thread;
//2.创建线程
/*
第一个参数:线程对象 传递地址
第二个参数:线程的属性 NULL
第三个参数:指向函数的指针
第四个参数:函数需要接受的参数
*/
pthread_create(&thread, NULL, task, NULL);
void *task(void *param)
{
// NSLog(@"%@--------",[NSThread currentThread]);
return NULL;
}
- NSThread
方法一:
//1.alloc init 创建线程,需要手动启动线程
//线程的生命周期:当任务执行完毕之后被释放掉
//1.创建线程
/*
第一个参数:目标对象 self
第二个参数:方法选择器 调用的方法
第三个参数:前面调用方法需要传递的参数 nil
*/
NSThread *threadA = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"ABC"];
//设置属性
threadA.name = @"线程A";
//设置优先级 取值范围 0.0 ~ 1.0 之间 最高是1.0 默认优先级是0.5
threadA.threadPriority = 1.0;
//2.启动线程
[threadA start];
方法二 :
//2.分离子线程,自动启动线程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"分离子线程"];
方法三:
//3.开启一条后台线程
[self performSelectorInBackground:@selector(run:) withObject:@"开启后台线程"];
-(void)run:(NSString *)param
{
// NSLog(@"---run----%@---%@",[NSThread currentThread].name,param);
for (NSInteger i = 0; i<10000; i++) {
NSLog(@"%zd----%@",i,[NSThread currentThread].name);
}
}
线程状态:
//阻塞线程
//[NSThread sleepForTimeInterval:2.0];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];
// [NSThread exit]; //退出当前线程
多线程的安全隐患:
资源共享
1块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
比如多个线程访问同一个对象、同一个变量、同一个文件
当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题
self.totalCount = 100;
self.threadA = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadB = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadC = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
self.threadA.name = @"售票员A";
self.threadB.name = @"售票员B";
self.threadC.name = @"售票员C";
//启动线程
[self.threadA start];
[self.threadB start];
[self.threadC start];
-(void)saleTicket
{
while (1) {
//锁:必须是全局唯一的
//1.注意枷锁的位置
//2.注意枷锁的前提条件,多线程共享同一块资源
//3.注意加锁是需要代价的,需要耗费性能的
//4.加锁的结果:线程同步
@synchronized(self) {
//线程1
//线程2
//线程3
NSInteger count = self.totalCount;
if (count >0) {
for (NSInteger i = 0; i<1000000; i++) {
}
self.totalCount = count - 1;
//卖出去一张票
NSLog(@"%@卖出去了一张票,还剩下%zd张票", [NSThread currentThread].name,self.totalCount);
}else
{
NSLog(@"不要回公司上班了");
break;
}
}
}
}
- GCD:
纯C语言的函数, 提供了非常强大的函数
优势:
- GCD是苹果公司为多核的并行运算提供的解决方案
- GCD会自动的利用更多的CPU 内核
- GCD会自动的管理线程的生命周期
- 程序员只需要告诉GCD 想要执行什么任务, 并不需要编写线程管理代码
- 任务: 执行什么操作, 遵循 FIFO 原则
- 队列: 用来存放任务
-
并发队列: 可以让多个任务并发执行(自动开启线程同时执行任务), 但是只有在异步函数下才有效
-
串行队列: 让任务一个个的执行(一个任务执行完毕之后, 再执行下一个任务)
-
同步: 只能在当前线程中执行任务, 不开启新的线程
-
异步: 可以在新的线程中执行任务, 具备开启新线程的能力
-
GCD 的基本使用
// 1. 异步函数+并发队列:会开启多条线程,队列中的任务是并发执行
-(void)asyncConcurrent
{
//1.创建队列
/*
第一个参数:C语言的字符串,标签
第二个参数:队列的类型
DISPATCH_QUEUE_CONCURRENT:并发
DISPATCH_QUEUE_SERIAL:串行
*/
//dispatch_queue_t queue = dispatch_queue_create("com.sina", DISPATCH_QUEUE_CONCURRENT);
//获得全局并发队列
/*
第一个参数:优先级
第二个参数:
*/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"---satrt----");
//2. 封装任务
/*
第一个参数:队列
第二个参数:要执行的任务
*/
dispatch_async(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
NSLog(@"---end----");
}
// 2. 异步函数+串行队列:会开线程,开一条线程,队列中的任务是串行执行的
-(void)asyncSerial
{
//1.创建队列
dispatch_queue_t queue = dispatch_queue_create("com.sina", DISPATCH_QUEUE_SERIAL);
//2.封装操作
dispatch_async(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
}
// 3. 同步函数+并发队列:不会开线程,任务是串行执行的
-(void)syncConcurrent
{
//1.创建队列
dispatch_queue_t queue = dispatch_queue_create("com.sina", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"---start---");
//2.封装任务
dispatch_sync(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
NSLog(@"---end---");
}
// 4. 同步函数+串行队列:不会开线程,任务是串行执行的
-(void)syncSerial
{
//1.创建队列
dispatch_queue_t queue = dispatch_queue_create("com.sina", DISPATCH_QUEUE_SERIAL);
//2.封装任务
dispatch_sync(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
}
主队列相关:
// 1. 异步函数+主队列:所有任务都在主线程中执行,不会开线程
-(void)asyncMain
{
//1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//2.异步函数
dispatch_async(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
}
// 2. 同步函数+主队列:死锁
//注意:如果该方法在子线程中执行,那么所有的任务在主线程中执行,
-(void)syncMain
{
//1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
NSLog(@"start----");
//2.同步函数
//同步函数:立刻马上执行,如果我没有执行完毕,那么后面的也别想执行
//异步函数:如果我没有执行完毕,那么后面的也可以执行
dispatch_sync(queue, ^{
NSLog(@"download1----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download2----%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"download3----%@",[NSThread currentThread]);
});
NSLog(@"end---");
}
同步异步.jpg
GCD 线程之间的通信:
//1.创建子线程下载图片
//DISPATCH_QUEUE_PRIORITY_DEFAULT 0
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//1.1 确定url
NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1511338633697&di=294a5b722555e26ba1c5c08b60b4a8b7&imgtype=0&src=http%3A%2F%2Fimg.sucaifengbao.com%2Fvector%2Flogosjbz%2F31_376_bp.jpg"];
//1.2 下载二进制数据到本地
NSData *imageData = [NSData dataWithContentsOfURL:url];
//1.3 转换图片
UIImage *image = [UIImage imageWithData:imageData];
NSLog(@"download----%@",[NSThread currentThread]);
//更新UI
// dispatch_async(dispatch_get_main_queue(), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"UI----%@",[NSThread currentThread]);
});
});
GCD 延迟执行
//延迟执行
-(void)delay
{
NSLog(@"start-----");
//1. 延迟执行的第一种方法
//[self performSelector:@selector(task) withObject:nil afterDelay:2.0];
//2.延迟执行的第二种方法
//[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];
//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]);
});
}
GCD 执行一次性代码
//一次性代码
//不能放在懒加载中的,应用场景:单例模式
-(void)once
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"---once----");
});
}
GCD 栅栏函数
//0.获得全局并发队列
//栅栏函数不能使用全局并发队列
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);
//1.异步函数
dispatch_async(queue, ^{
for (NSInteger i = 0; i<100; i++) {
NSLog(@"download1-%zd-%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<100; i++) {
NSLog(@"download2-%zd-%@",i,[NSThread currentThread]);
}
});
//栅栏函数
dispatch_barrier_async(queue, ^{
NSLog(@"+++++++++++++++++++++++++++++");
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<100; i++) {
NSLog(@"download3-%zd-%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<100; i++) {
NSLog(@"download4-%zd-%@",i,[NSThread currentThread]);
}
});
GCD 中的 apply
//开子线程和主线程一起完成遍历任务,任务的执行时并发的
-(void)apply
{
/*
第一个参数:遍历的次数
第二个参数:队列(并发队列)
第三个参数:index 索引
*/
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"%zd---%@",index,[NSThread currentThread]);
});
}
GCD 中的 group
-(void)group1
{
//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, ^{
NSLog(@"1----%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"2----%@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"3----%@",[NSThread currentThread]);
});
//拦截通知,当队列组中所有的任务都执行完毕的时候回进入到下面的方法
dispatch_group_notify(group, queue, ^{
NSLog(@"-------dispatch_group_notify-------");
});
// NSLog(@"----end----");
}
-(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, ^{
NSLog(@"1----%@",[NSThread currentThread]);
//离开群组
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"2----%@",[NSThread currentThread]);
//离开群组
dispatch_group_leave(group);
});
//拦截通知
//问题?该方法是阻塞的吗? 内部本身是异步的
// dispatch_group_notify(group, queue, ^{
// NSLog(@"-------dispatch_group_notify-------");
// });
//等待.死等. 直到队列组中所有的任务都执行完毕之后才能执行
//阻塞的
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"----end----");
}
-(void)group3
{
/*
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,^{
NSLog(@"download1---%@",[NSThread currentThread]);
//1.1 确定url
NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1511338633697&di=294a5b722555e26ba1c5c08b60b4a8b7&imgtype=0&src=http%3A%2F%2Fimg.sucaifengbao.com%2Fvector%2Flogosjbz%2F31_376_bp.jpg"];
//1.2 下载二进制数据
NSData *imageData = [NSData dataWithContentsOfURL:url];
//1.3 转换图片
self.image1 = [UIImage imageWithData:imageData];
});
// 2.下载图片2 开子线程
dispatch_group_async(group, queue,^{
NSLog(@"download2---%@",[NSThread currentThread]);
//2.1 确定url
NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1511338633697&di=294a5b722555e26ba1c5c08b60b4a8b7&imgtype=0&src=http%3A%2F%2Fimg.sucaifengbao.com%2Fvector%2Flogosjbz%2F31_376_bp.jpg"];
//2.2 下载二进制数据
NSData *imageData = [NSData dataWithContentsOfURL:url];
//2.3 转换图片
self.image2 = [UIImage imageWithData:imageData];
});
//3.合并图片
//主线程中执行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"combie---%@",[NSThread currentThread]);
//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;
// });
});
// dispatch_release(group)
}
- NSOperation
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类- 使用NSOperation子类的方式有3种
NSInvocationOperation
NSBlockOperation
自定义子类继承NSOperation,实现内部相应的方法
- 使用NSOperation子类的方式有3种
//1.创建操作,封装任务
/*
第一个参数:目标对象 self
第二个参数:调用方法的名称
第三个参数:前面方法需要接受的参数 nil
*/
NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil];
NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download3) object:nil];
或者
//1.创建操作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1----%@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2----%@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3----%@",[NSThread currentThread]);
}];
//2.启动|执行操作
[op1 start];
[op2 start];
[op3 start];
//简便方法
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//1)创建操作,2)添加操作到队列中
[queue addOperationWithBlock:^{
NSLog(@"7----%@",[NSThread currentThread]);
}];
//设置最大并发数量 maxConcurrentOperationCount
//同一时间最多有多少个任务可以执行
//串行执行任务!=只开一条线程 (线程同步)
// maxConcurrentOperationCount >1 那么就是并发队列
// maxConcurrentOperationCount == 1 那就是串行队列
// maxConcurrentOperationCount == 0 不会执行任务
// maxConcurrentOperationCount == -1 特殊意义 最大值 表示不受限制