iOS基础--多线程简单总结(NSThread、NSOperation、NSOperationQueue))
多线程概念
- 程序: 由源代码生成的可执行应用.
- 进程: 一个正在运行的程序可以看做一个进程. (例如: 正在运行的QQ就是一个进程) , 进程拥有独立运行所需的全部资源.
- 线程: 程序中独立运行的代码段.
注:一个进程是由一或多个线程组成. 进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行.
- 单线程:
1、每个正在运行的程序(即 进程),至少包含一个线程, 这个线程叫 主线程
2、主线程在程序启动时被创建,用于执行mian函数
3、只有一个主线程的程序,称作单线程程序
3、在单线程程序中,主线程负责执行程序的所有代码(UI 展现以及刷新,网络请求, 本地存储等). 这些代码只能顺序执行, 无法并发执行 .
- 多线程:
1、拥有多个线程的程序,称作多线程程序
2、iOS 允许用户自己开辟新的线程, 相对于主线程来讲, 这些线程, 称作子线程
3、可以根据需要开辟若干子线程
4、子线程和主线程 都是 独立 的运行单元, 各自的执行互不影响, 因此能够并发执行
- 单线程,多线程区别 :
1、单线程程序: 只有一个线程, 即主线程, 代码顺序执行,容易出现代码阻塞(页面假死)
2、多线程程序: 有多个线程, 线程间独立运行, 能有效的避免代码阻塞,并且提高程序的运行性能.
注意: iOS关于UI的添加和刷新必须在主线程中操作.//开发中依赖于多线程: 网络 :(主线程:(UI),子线程(取数据))
- iOS多线程实现种类
NSObject
NSThread
NSOperationQueue
GCD
NSObject 和 NSThread
#pragma mark ---------NSObject 开辟子线程
// NSObject 开辟子线程
// 参数 1: selector, 子线程执行的代码(方法名)
// 参数 2: 表示selector传递的参数[self performSelectorInBackground:@selector(sayHi) withObject:@"hahah"];
#pragma mark ---------NSThread 手动开辟子线程
// NSThread 开辟一个子线程
// 参数 1: target
// 参数 2: action
// 参数 3: 传参NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(sayHi) object:nil];
//开启子线程 [thread start];
//取消 (给线程发送结束消息,不会真正的取消掉线程,而是标记 这个被取消了)[thread cancel];
//立即结束线程[NSThread exit];
//对于NSObject 和 NSThread实现的多线程,在任务完成之后,线程会被自动释放
//判断一个线程是否正在执行[thread isExecuting];
//判断一个线程是否完成了任务(是否执行完毕)[thread isFinished];
#pragma mark -----------NSThread 自动开辟一个线程
//使用NSThread自动开辟一个线程
//不需要手动开启线程[NSThread detachNewThreadSelector:@selector(sayHi) toTarget:self withObject:nil];
//几秒后执行某件事情[self performSelector:@selector(time) withObject:self afterDelay:3.0f];
}
//让线程休眠2秒[NSThread sleepForTimeInterval:2];
- (void)sayHi{
// [NSThread currentThread]; 获取当前的线程NSLog(@" %@ ", [NSThread currentThread]);
// [NSThread mainThread]; 获取主线程NSLog(@" %@ ", [NSThread mainThread]);
// [NSThread isMainThread] 判断当前线程是不是主线程NSLog(@" %d ", [NSThread isMainThread]);
#pragma mark ----------NSObject
//NSObject中回到主线程去做某事
// 参数 1: 回到主线程做的事情
// 参数 2: 传递的参数
// 参数 3: NO:当前的线程任务已经结束才去做 YES: 执行完 selector任务后才执行当前线程的其他任务[self performSelectorOnMainThread:@selector(onMainThread) withObject:nil waitUntilDone:YES];
\ #pragma mark -----------NSThread 手动for (int i = 0; i < 10000; i++) { NSLog(@" %d ", i);
if (i == 5000) {
//关闭线程
// 写在哪里 哪个线程就关闭了, 注意 不要随意的使用. 使用的时候一定要注意当前的线程 是否主线程[NSThread exit];
}
}
}
- (void)onMainThread{
self.view.backgroundColor = [UIColor orangeColor];
NSLog(@"mian --> %@ ", [NSThread mainThread]);
NSLog(@"current --> %@", [NSThread currentThread]);
}```
-----
NSOperation和NSOperationQueue
------
------
- NSOperation :
>1、NSOperation类, 在MVC中属于M, 是用来封装单个任务相关的代码和数据的抽象类
2、因为它抽象的,不能够直接使用这个类,而是使用子类(NSInvocationOperation或NSBlockOperation) 来执行实际任务.
3、NSOperation(含子类), 只是一个操作, 本身无主线, 子线程之分, 可在任意线程中使用. 通常与NSOperationQueue结合使用.
- NSOperationQueue
>1、NSOperationQueue是操作队列,它用来管理一组Operation对象的执行,会根据需要自动为Operation开辟合适数量的线程,以完成任务并行执行
2、其中NSOperation可以调节它在队列中的优先级 (使用addDependency: 设置依赖关系)
3、当最大并发数设置为1的时候,能实现线程同步 (串行执行);
-------
//NSOperation 是一个抽象类,不能直接使用
//NSOperation类及其子类本身不会进行线程的创建
\#pragma mark —————NSInvocationOperation
//通过NSInvocationOperation类来创建一个NSOperation对象
```code
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(hehehe) object:nil];`
//operation 在单独使用的时候 一定要调用开始方法
` [operation start];```
\#pragma mark —————NSBlockOperation
```code
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block main --> %@", [NSThread mainThread]);
NSLog(@"block current --> %@", [NSThread currentThread]); }];
//operation 在单独使用的时候 一定要调用开始方法
[blockOperation start];```
\#pragma mark —————NSOperationQueue
` NSOperationQueue *queue = [[NSOperationQueue alloc]init];`
//队列添加operation子类,并调用方法
` [queue addOperation:operation];
[queue addOperation:blockOperation]; `
//依赖关系 (只有 参数线程 执行完,才能执行,之前是随机交错进行的)
` [operation addDependency:blockOperation];`
//获取 系统提供的队列
```code
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];}
- (void)hehehe{
NSLog(@"hehehe main --> %@", [NSThread mainThread]);
NSLog(@"hehehe current --> %@", [NSThread currentThread]);
NSLog(@" %d ", [NSThread isMainThread]);
}```
```code
- (void)touchesBegan:(NSSet<UITouch *> \*)touches withEvent:(UIEvent \*)event{
//NSOperationQueue 是一个队列管理器,可以根据operation任务自己,分配线程,自己管理线程的生命周期
//在开发过程中,我们不需要管理线程的创建和销毁
//NSOperationQueue 创建的是n个并行的线程
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//最大线程并发数
//设置这个参数之后,NSOperationQueue表示同时执行任务的最大数量
//即使只执行一个任务,系统有时候也会开辟多个线程去执行这个任务
queue.maxConcurrentOperationCount = 1;
for (int i = 0; i < 10; i++) ``
{
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"current --> %@ main --> %@",[NSThread currentThread],[NSThread mainThread]); }];
[queue addOperation:blockOperation]; }}```
------
GCD
-------
--------
##Grand Central Dispatch (GCD)是Apple开发的一种多核编程技术.主要用于优化应用程序以支持多核处理器以及其他对称处理系统.
###GCD提供函数实现多线程开发,性能更好,功能也更加强大.
- ###核心概念:
1、任务: 具有一个订功能的代码段.一般是一个block或者函数
2、分发队列: GCD以队列的方式进行工作,FIFO(先入先出队列)
3、GCD会根据分发队列的类型, 创建合适数量的线程执行队列中的任务
- ###GCD中的两种队列 dispatch_queue:
- SerialQueue(串行) : 一次只执行一个任务. Serial queue通常用于同步访问,特定的资源或数据.当你创建多个Serial queue时,虽然他们各自是同步执行的,但Serial queue于Serial queue之间是并发执行的.SerialQueue能实现线程同步.
- Concurrent(并发) : 可以并发地执行多个任务,但是遵守FIFO(先入先出队列 )
--------
--------
\#pragma mark ----------GCD串行队列
// 1) 系统提供的一个串行队列
// 使用系统提供的串行队列 (主队列,也就是在主线程里一次执行任务)
` dispatch_queue_t queue = dispatch_get_main_queue();`
// 2) 创建一个串行队列
// 参数 1: 自己创建队列的名(苹果推荐使用反向域名去命名注意没有@)
// 参数 2: 系统提供好的一个宏(DISPATCH_QUEUE_SERIAL=NULL)
// 这种方式创建的队列,会开辟子线程去执行任务
` dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);`
------
------
\#pragma mark ----------GCD并行队列 // 1) 使用系统提供的并行队列
// 参数 1: 表示队列的优先级 (
DISPATCH_QUEUE_PRIORITY_BACKGROUND,
DISPATCH_QUEUE_PRIORITY_LOW,
DISPATCH_QUEUE_PRIORITY_HEIGH,
DISPATCH_QUEUE_PRIORITY_DEFAULT)
// 参数 2: 系统保留字段
`dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);`
// 2) 创建并行队列
` dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);`
--------
--------
\#pragma mark ----------GCD 功能
//使用dispatch_async() 向队列添加任务,任务会排队执行
// 任务虽会顺序排队执行,但如果用并发队列(CONCURRENT),可能输出顺序不一样.
```code
dispatch_async(queue, ^{
NSLog(@"1 mian --> %@, current --> %@",[NSThread mainThread],[NSThread currentThread]); });
dispatch_async(queue, ^{
NSLog(@"2 mian --> %@, current --> %@",[NSThread mainThread],[NSThread currentThread]); });
dispatch_async(queue, ^{
NSLog(@"3 mian --> %@, current --> %@",[NSThread mainThread],[NSThread currentThread]);
}); ```
// dispatch_after()
//往队列中添加任务,任务不但会排队,还会在延迟的时间点执行
```code
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"已经3秒之后了");
});```
//dispatch_apply();
//往队列中添加任务,任务会重复执行n次
// 参数 1: 一共执行次数
// 参数 2: 执行的队列
// 参数 3: 当前索引
```code
dispatch_apply(3, queue, ^(size_t index) {
NSLog(@" %zu ", index);
});```
//分组
//创建一个分组
`dispatch_group_t group = dispatch_group_create(); `
//创建一个队列
`dispatch_queue_t queue = dispatch_queue_create("000", DISPATCH_QUEUE_CONCURRENT); `
//向分组中添加一个任务
` dispatch_group_async(group, queue, ^{
NSLog(@"1"); }); `
//向分组添加 最后执行的任务(不能添加为第一个)
`dispatch_group_notify(group, queue, ^{
NSLog(@"last one");
})`
//将任务添加到队列,此任务执行的时候,其他任务停止执行,所以它输出顺序不改变
```code
dispatch_barrier_async(queue, ^{
NSLog(@"不变位置的2"); });
dispatch_group_async(group, queue, ^{
NSLog(@"3");
});
}```
####**dispatch_once() //将任务添加到队列, 但任务在程序运行过程中,只执行一次**
//创建单例类
\#import "MyObject.h"static MyObject *object = nil;
\+ (MyObject *)sharedMyObject{
//表示同意时间内, 只有一个线程可以访问block块里面的内容
//dispatch_once 系统封装好的代码块
```code
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (object == nil) {
object = [MyObject new]; } });
return object;}```
>dispatch_sync() //将任务添加到队列, block不执行完,下面代码不会执行
dispatch_async() //将任务添加到队列,不等内部block执行完,就去执行下面代码
dispatch_async_f() //将任务添加到队列, 任务是函数,非Block
--------
线程间的通信
---------
---------
>线程间通信分为两种:
- 主线程进入子线程 (前面的方法都可以)
- 子线程回到主线程
\#pragma mark ----------NSObject
\ -(void)viewDidLoad{
//NSObject中回到主线程去做某
// 参数 1: 回到主线程做的事情
// 参数 2: 传递的参数
// 参数 3: 知道当前的线程已经结束才去做
```code
[self performSelectorOnMainThread:@selector(onMainThread) withObject:nil waitUntilDone:YES];
}`
\- (void)onMainThread{
`self.view.backgroundColor = [UIColor orangeColor];
NSLog(@"mian --> %@ ", [NSThread mainThread]);
NSLog(@"current --> %@", [NSThread currentThread]);`
}
\#pragma mark —————GCD
\- (void)loadData{
`NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error == nil) {
dispatch_async(dispatch_get_main_queue(), ^{
//这里去做更新UI的事情
}); } }];
}```
--------
- ###线程互斥
> 1、多线程并行编程中,线程间同步与互斥是一个很有技巧也是很容易出错的地方.
2、线程间互斥应对的是这种场景: 多个线程操作统一资源(即某个对象),需要保证线程在对资源的状态(即对象的成员变量)进行一些非原子性操作后,状态仍然正确.