欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS基础--多线程简单总结(NSThread、NSOperation、NSOperationQueue))

程序员文章站 2022-04-13 12:14:23
...
iOS基础--多线程简单总结(NSThread、NSOperation、NSOperationQueue))
原来一切都来不及
iOS基础--多线程简单总结(NSThread、NSOperation、NSOperationQueue))
多线程.png

多线程概念


  • 程序: 由源代码生成的可执行应用.
  • 进程: 一个正在运行的程序可以看做一个进程. (例如: 正在运行的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、线程间互斥应对的是这种场景: 多个线程操作统一资源(即某个对象),需要保证线程在对资源的状态(即对象的成员变量)进行一些非原子性操作后,状态仍然正确.