关于iOS多线程浅析
多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个执行绪,进而提升整体处理性能。 —— 维基百科
- 多线程概念
- 概念
- 例子
- 目的
- 优缺点
- 多线程的同步异步
- 同步
- 异步
- 多线程的线程进程
- 线程
- 进程
- 多线程的方式
- PThread
- NSThread
- GCD
- GCD的概念
- GCD的简单使用
- GCD的任务和队列
- NSOPeration
- NSOperation的简介
- NSOperation的简单使用
- NSOperation的高级功能
- 面试题
多线程的概念
-
概念
- 多线程:
一个进程中可以开启多条线程,多条线程可以同时执行不同的任务。
- 多线程:
-
例子
- 举个例子:
酷我音乐的边下载边听歌,迅雷的边下载边播放。
- 举个例子:
-
多线程目的
将耗时操作放在后台处理,保证UI界面的正常显示和交互。
网络操作是非常耗时的,在做网络开发,所有网络访问都是耗时操作.需要在后台线程中执行。
多线程开发的原则:越简单越好。
-
多线程优缺点
- 优点:
能‘适当’提高程序的执行效率,能适当提高CPU的内存利用率,线程上的任务执行完成后,线程会自动销毁节省内存。
- 缺点:
如果开启线程过多会占用大量CPU资源降低程序性能。
- 优点:
同步异步(同步和异步是两种执行任务的方式。)
-
同步
- 同步:
代码从上到下顺序执行就叫做同步执行(多个任务依次执行)。
- 同步:
-
异步
- 异步:
多个任务同时执行就是异步执行。
- 异步:
多线程的线程进程
-
进程
- 进程:
在系统中正在运行的一个程序叫做进程,进程可以类比成正在正常运营的公司。
- 进程:
-
线程
- 线程:
线程可以类比成公司里的员工,程序启动默认会开启一个线程。
- 线程:
多线程的方式
-
PThread(几乎不用)
Pthreads定义了一套C语言的类型、函数与常量,它以pthread.h头文件和一个线程库实现。
-
NSThread(很少使用)
NSThread是基于线程使用,轻量级的多线程编程方法(相对GCD和NSOperation),一个NSThread对象代表一个线程,需要手动管理线程的生命周期,处理线程同步等问题。
-
GCD(经常使用)
- GCD的概念
- 什么是GCD:
全称是Grand Central Dispatch,纯C语言的,提供了非常多强大的函数。
- GCD的核心:
将任务添加到队列。
- GCD使用的两个步骤:
创建任务,确定要做的事情,GCD中的任务是使用BLOCK封装的。将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行。任务的取出遵循队列的FIFO原则 : 先进先出,后进后出
- 什么是GCD:
-
GCD的简单使用
-
任务添加到队列
- (void)GCDDemo1 { // 1. 创建队列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 2. 创建任务 : 用block指定的 (无参无返回值的) void (^task)() = ^ { NSLog(@"%@",[NSThread currentThread]); }; // 3. 把任务添加到队列 // dispatch_async : 表示任务是异步的 // dispatch_sync : 表示任务是同步的 dispatch_async(queue, task); }
-
简写
- (void)GCDDemo2 { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"%@",[NSThread currentThread]); }); }
-
线程间的通信
- (void)GCDDemo4 { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSLog(@"假装在努力下载...%@",[NSThread currentThread]); // 下载结束之后,回到主线程更新UI dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"假装在更新UI...%@",[NSThread currentThread]); }); }); }
-
使用GCD的线程间的通信实现异步下载网络图片
-
- GCD的概念
- (void)downloadImage
{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"downloadImage %@",[NSThread currentThread]);
// URL
NSURL *URL = [NSURL URLWithString:@"http://atth.eduu.com/album/201203/12/1475134_1331559643qMzc.jpg"];
// data
NSData *data = [NSData dataWithContentsOfURL:URL];
// image
UIImage *image = [UIImage imageWithData:data];
// 拿到图片对象之后,回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"updateUI %@",[NSThread currentThread]);
self.myImageView.image = image;
[self.myImageView sizeToFit];
[self.myScrollView setContentSize:image.size];
});
});
}
-
GCD的任务和队列
- GCD的任务:
- 同步的方式执行任务 :
在当前线程中依次执行任务。
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 异步的方式执行任务 :
新开线程在新线程中执行任务。
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 同步的方式执行任务 :
- GCD队列:
- 串行队列:
让任务一个接着一个有序的执行:不管队列里面放的是什么任务,一个任务执行完毕后,再执行下一个任务,同时只能调度一个任务执行。
- 并发队列:
可以让多个任务并发/同时执行,自动开启多个线程同时执行多个任务,同时可以调度多个任务执行。并发队列的并发功能只有内部的任务是异步任务时,才有效。
- 串行队列:
- GCD的任务:
-
代码小结
串行队列+同步任务
/*
1.不开线程
2.有序执行
*/
- (void)GCDDemo1
{
/*
创建串行队列
参数1 : 队列的标识符
参数2 : 队列的属性,决定了队列是串行的还是并行的
DISPATCH_QUEUE_SERIAL : 串行队列
*/
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
// 循环的创建了10个同步任务,添加到队列
for (NSInteger i = 0; i<10; i++) {
// 把同步任务添加到串行队列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
串行队列+异步任务
- (void)GCDDemo2
{
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_SERIAL);
for (NSInteger i = 0; i<10; i++) {
// 把异步任务添加到串行队列
dispatch_async(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"嘿嘿嘿");
}
并行队列+同步任务
/*
不开线程
有序执行
*/
- (void)GCDDemo1
{
// 创建并行队列
// DISPATCH_QUEUE_CONCURRENT : 并行队列
// 并行队列只能决定"是否"可以同时调度多个任务;不能决定开不开线程
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把同步任务添加到并行队列
dispatch_sync(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"哈哈哈");
}
并行队列+异步任务
/*
开线程
无序执行
*/
- (void)GCDDemo2
{
// 并行队列
dispatch_queue_t queue = dispatch_queue_create("GL", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i<10; i++) {
// 把异步任务添加到并发队列
dispatch_async(queue, ^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
});
}
NSLog(@"嘿嘿嘿");
}
Paste_Image.png
-
NSOperation(经常使用)
-
NSOperation的简介
是OC语言中基于GCD的面向对象的封装,使用起来比GCD更加简单。提供了一些GCD不好实现的功能,苹果推荐使用。NSOperation还不用关心线程和线程的声明周期。
NSOperation是个抽象类无法直接使用。因为方法只有声明没有实现。
子类:NSInvocationOperation和NSBlockOperation,自定义NSOperation操作默是异步的。
队列 : NSOperationQueue队列默认是并发的。
核心:GCD的核心 : 将任务添加到队列中。OP的核心 : 将操作添加到队列中。
-
NSOperation的简单使用
先将需要执行的操作封装到一个NSOperation对象中,创建NSOperation对象。
将NSOperation对象添加到NSOperationQueue中。
NSOperationQueue会自动将NSOperation取出来
将取出的NSOperation封装的操作自动放到一条对应的新线程中执行。
-
NSOperation的高级功能
- 最大并发数
-
设置最大并发数
// 队列的最大并发数的属性
// 作用 : 控制队列同时调度任务执行的个数;
// 间接控制了线程的数量;
// 注意 : 队列的最大并发数,不是线程数;
@implementation ViewController {
/// 全局队列
NSOperationQueue *_queue;
}
- (void)viewDidLoad {
[super viewDidLoad];
_queue = [[NSOperationQueue alloc] init];
// 设置队列的最大并发数 : 至少开两个
_queue.maxConcurrentOperationCount = 2;
}
演示
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self GCDDemo];
}
- (void)GCDDemo
{
for (NSInteger i = 0; i<50; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%zd %@",i,[NSThread currentThread]);
}];
[_queue addOperation:op];
}
}
执行结果:任务是两个两个的执行。
继续、暂停、取消全部
- 在最大并发数代码的基础上增加暂停、继续、取消。
#pragma 取消全部
/*
1.正在执行的操作无法被取消;
2.如果非要取消正在执行的操作,需要自定义NSOperation
3.这个取消全部的操作有一定的时间延迟
*/
- (IBAction)cancelAll:(id)sender
{
// 移除队列里面"所有"的操作
[_queue cancelAllOperations];
NSLog(@"取消全部 %tu",_queue.operationCount);
}
#pragma 继续
- (IBAction)jixu:(id)sender
{
// 不挂起队列,使队列继续调度任务执行
_queue.suspended = NO;
NSLog(@"继续 %tu",_queue.operationCount);
}
#pragma 暂停
/*
1.正在执行的操作无法被暂停
2.operationCount : 队列里面的操作个数;统计的是队列里面还没有执行完的操作;
3.队列里面的任务一旦执行完,会从队列里面移除;
*/
- (IBAction)zanting:(id)sender
{
// 挂起队列,使队列暂停调度任务执行
_queue.suspended = YES;
NSLog(@"暂停 %tu",_queue.operationCount);
}
- 暂停队列结论
将队列挂起之后,队列中的操作就不会被调度,但是正在执行的操作不受影响。operationCount:操作计数,没有执行和没有执行完的操作,都会计算在操作计数之内。注意:如果先暂停队列,再添加操作到队列,队列不会调度操作执行。所以在暂停队列之前要判断队列中有没有任务,如果没有操作就不暂停队列。
- 取消队列结论
一旦调用的 cancelAllOperations方法,队列中的操作,都会被移除,正在执行的操作除外。 正在执行的操作取消不了,如果要取消,需要自定义NSOperation。 队列取消全部操作时,会有一定的时间延迟。
- 操作间依赖关系。
场景:登陆-->付费-->下载-->通知用户
准备需要执行的操作
// 登录
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"登录 %@",[NSThread currentThread]);
}];
// 付费
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"付费 %@",[NSThread currentThread]);
}];
// 下载
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载 %@",[NSThread currentThread]);
}];
// 通知用户
NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"通知用户 %@",[NSThread currentThread]);
}];
添加依赖(核心代码)
/*
添加依赖关系
1.不能在操作添加到队列之后,在建立依赖关系;因为已经晚了
2.可以跨队列建立依赖关系
3.不能建立循环依赖
*/
[op2 addDependency:op1]; // 付费依赖登录
[op3 addDependency:op2]; // 下载依赖付费
[op4 addDependency:op3]; // 通知用户依赖下载
// [op1 addDependency:op4]; // 登录依赖通知用户 : 循环依赖;会卡死
// 批量把操作添加到队列
// waitUntilFinished : 是否等待前面的异步任务执行完,在执行后面的代码
[_queue addOperations:@[op1,op2,op3] waitUntilFinished:NO];
// 一个操作不能同时添加到两个队列
[[NSOperationQueue mainQueue] addOperation:op4];
-
结论
不能循环建立操作间依赖关系,否则队列不调度操作执行。
操作间可以跨队列建立依赖关系。
要将操作间的依赖建立好了之后,再添加到队列中,先建立操作依赖关系,再把操作添加到队列。
面试题
-
面试题仅供参考
用 NSOPeration 和 NSOpertionQueue 处理 A,B,C 三个线程,要求执行完 A,B 后才能执行 C, 怎么做?
添加操作依赖,C依赖于A同时依赖于B。创建操作队列,将操作添加到操作队列中。
线程间怎么通信?
什么是线程通信:不同线程之间传递数据,一般线程传递到主线程。 iOS中开启多线程的方式:三种。比如:在子线程下载图片,然后回到主线程显示图片。
简述多线程的作用以及什么地方会用到多线程?OC实现多线程的方法有哪些?谈谈多线程安全问题的几种解决方案?何为线程同步,如何实现的?分线程回调主线程方法是什么,有什么作用?
1. 耗时操作、界面卡死的时候使用多线程
2. 作用:可以同时执行多个任务,适当提高程序的执行效率。为了提高CPU的使用率,采用多线程的方式去同时完成几件事互不干扰。
3. iOS中多线程的方法:NSThread、NSOperation、GCD、pthread
4. 使用场景:同时上传和下载多个文件:加载网络数据同时展示Loading的UI、大量数据I/O操作。
5. 资源共享造成的安全问题:多线程环境下,当多个线程同时操作共享资源的setter和getter方法时,会造成数据的读写错乱就是线程安全问题。
6. 线程同步技术:使多个线程一次有序的执行,实现方案是加锁,把共享资源的读写操作锁起来常用的是互斥锁。
7. 线程间的通信:一个线程执行完任务之后,把执行的结果传递到另外一个线程叫线程间通信。线程间通信可用来在两个线程间传递数据。
感谢读到最后的朋友,最后请点赞支持一下,谢谢!