美文网首页iOS 知识点原理
问题:请简单介绍下iOS的多线程

问题:请简单介绍下iOS的多线程

作者: 姜小舟 | 来源:发表于2020-05-15 11:06 被阅读0次

    iOS的三种多线程技术

    • NSThread:每个NSThread对象对应一个线程,量级较轻(真正的多线程)
    • NSOperation/NSOperationQueue 面向对象的线程技术
    • GCD(Grand Central Dispatch) 是基于C语言的框架,可以充分利用多核,是苹果推荐使用的多线程技术

    以上这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的,在项目中很多框架技术分别使用了不同多线程技术。

    三种多线程技术的对比

    NSThread:

    • 优点:NSThread 比其他两个轻量级,使用简单
    • 缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销

    NSOperation:

    • 优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上
    • 缺点:面向对象的抽象类,只能实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。

    GCD(Grand Central Dispatch):

    • 优点:GCD是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用,是替代NSThreadNSOperation的高效和强大的技术。最高效,避开并发陷阱。
    • 缺点:GCD是基于C语言的

    选择小结:

    • 简单而安全的选择NSOperation实现多线程即可。
    • 处理大量并发数据,又追求性能效率的选择GCD。
    • NSThread本人选择基本上是在做些小测试上使用,当然也可以基于此造个轮子。

    三种多线程技术的实现

    NSThread的多线程技术

    1. 静态实例化。类方法直接开启后台线程,并执行选择器方法detachNewThreadSelector
    // 新建一个线程,调用@selector方法
    [NSThread detachNewThreadSelector:@selector(bigDemo) toTarget:self withObject:nil];
    
    1. 动态实例化。成员方法,在实例化线程对象之后,需要使用start执行选择器方法initWithTarget
    // 成员方法
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(bigDemo) object:nil]; 
    // 启动start线程
    [thread start];
    
    1. 隐式实例化。对于NSThread的简单使用,可以用NSObject的performSelectorInBackground替代
    // performSelectorInBackground是将bigDemo的任务放>在后台线程中执行
    [self performSelectorInBackground:@selector(bigDemo) withObject:nil];</pre>
    

    同时,在NSThread调用的方法中,同样要使用autoreleasepool进行内存管理,否则容易出现内存泄露。

    // 自动释放池 2 
    // 负责其他线程上的内存管理,在使用NSThread或者NSObject的线程方法时,一定要使用自动释放池 4 
    // 否则容易出现内存泄露。
    
    @autoreleasepool {
    
    }
    
    1. NSThread的拓展认识
    #获取当前线程
    NSThread *current = [NSThread currentThread];
    #获取主线程
    NSThread *main = [NSThread mainThread];   
    #暂停当前线程
    [NSThread sleepForTimeInterval:2];  
    
    #线程之间通信
    #在指定线程上执行操作
    [self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; 
    #在主线程上执行操作
    [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 
    #在当前线程执行操作
    [self performSelector:@selector(run) withObject:nil]; 
    

    NSOperation,面向对象的多线程技术

    一、使用步骤:
    • 1. 实例化操作
    // 实例化操作队列
    _queue = [[NSOperationQueue alloc] init];
    

    NSInvocationOperation

    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(opAction) object:nil];
    // 如果使用start,会在当前线程启动操作
    // [op1 start];  
    // 1\. 一旦将操作添加到操作队列,操作就会启动
    [_queue addOperation:op1];
    

    NSBlockOperation

    #pragma mark 模仿下载网络图像
    - (IBAction)operationDemo3:(id)sender {
       // 1\. 下载
      NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
          NSLog(@"下载 %@" , [NSThread currentThread]);
      }];
      // 2\. 滤镜
      NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
           NSLog(@"滤镜 %@" , [NSThread currentThread]);
        }]; 
       // 3\. 显示
       NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
           NSLog(@"更新UI %@" , [NSThread currentThread]); 
       }]; 
       // 添加操作之间的依赖关系,所谓“依赖”关系,就是等待前一个任务完成后,后一个任务才能启动   
       // 依赖关系可以跨线程队列实现 
       // 提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
       [op2 addDependency:op1]; 
       [op3 addDependency:op2]; 
    //    [op1 addDependency:op3];
    
       [_queue addOperation:op1]; 
       [_queue addOperation:op2]; 
       [[NSOperationQueue mainQueue] addOperation:op3]; 
    }
    
    • 2. 将操作添加到队列NSOperationQueue即可启动多线程执行
    [_queue addOperation:op1]; 
    [_queue addOperation:op2];
    
    二、更新UI使用主线程队列
    //两方式
    [NSOpeationQueue mainQueue] addOperation ^{
    };
    [[NSOperationQueue mainQueue] addOperation:op3];
    
    三、操作队列setMaxConcurrentOperationCount可以设置同时并发的线程数量!
    # 控制同时最大并发的线程数量
    [_queue setMaxConcurrentOperationCount:2];
    #提示:此功能仅有NSOperation有!
    
    四、使用addDependency可以设置任务的执行先后顺序,同时可以跨操作队列指定依赖关系
    //添加操作之间的依赖关系,所谓“依赖”关系,就是等待前一个任务完成后,后一个任务才能启动
    //依赖关系可以跨线程队列实现。
    #提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
    [op2 addDependency:op1]; 
    [op3 addDependency:op2];
    [op1 addDependency:op3];
    #提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
    

    GCD,C语言

    GCD就是为了在“多核”上使用多线程技术

    1. 要使用GCD,所有的方法都是dispatch开头的
    2. 名词解释
    • global 全局
    • queue 队列
    • async 异步
    • sync 同步
    1. 要执行异步的任务,就在全局队列中执行即可。dispatch_async 异步执行控制不住先后顺序。
    2. 关于GCD的队列
    • 4.1 全局队列 dispatch_get_global_queue
    #参数:优先级 DISPATCH_QUEUE_PRIORITY_DEFAULT 始终是 0
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    #可同步 可异步
    
    • 4.2 串行队列
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    #
    #是创建得到的,不能直接获取!只能同步!
    
    • 4.3 主队列 dispatch_get_main_queue
    dispatch_async(dispatch_get_main_queue(), ^{
       NSLog(@"main - > %@", [NSThread currentThread]); 
    });
    #只能同歩
    
    1. 异步和同步与方法名无关,与运行所在的队列有关!
      同步主要用来控制方法的被调用的顺序
    2. 种多线程实现
    • 6.1 后台执行线程创建
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
       [self loadImageSource:imgUrl1];
    });
    
    • 6.2 UI线程执行(只是为了测试,长时间加载内容不放在主线程)
    dispatch_async(dispatch_get_main_queue(), ^{
       [self loadImageSource:imgUrl1];
    });
    
    • 6.3 一次性执行(常用来写单例)
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
       [self loadImageSource:imgUrl1];
    });
    
    • 6.4 并发地执行循环迭代
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    size_t count = 10;
    dispatch_apply(count, queue, ^(size_t i) {
       NSLog(@"循环执行第%li次",i);
       [self loadImageSource:imgUrl1];
    });  
    
    • 6.5 延迟执行
    double delayInSeconds = 2.0;
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
       [self loadImageSource:imgUrl1];
    });  
    
    • 6.6 自定义dispatch_queue_t
    dispatch_queue_t urls_queue = >dispatch_queue_create("minggo.app.com", NULL);
    dispatch_async(urls_queue, ^{
       [self loadImageSource:imgUrl1];
    });
    
    1. 对比多任务执行
      异步加载图片是大部分app都要面对的问题,那么加载图片是按循序加载完之后才刷新UI呢?还是不安顺序加载UI呢?显然大部分的希望各自加载各自的图片,各自刷新。以下就是模拟这两种场景。
    • 7.1 先后执行,加载两张图片为例
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       UIImage *image1 = [self loadImage:imgUrl1];
       UIImage *image2 = [self loadImage:imgUrl2];
       dispatch_async(dispatch_get_main_queue(), ^{
           self.imageview1.image = image1;
           self.imageView2.image = image2;
       });
    });  
    
    • 7.2 并行队列执行,也是以加载两张图片为例
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
       dispatch_group_t group = dispatch_group_create();
       __block UIImage *image1 = nil;
       __block UIImage *image2 = nil;
    
       dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           image1 = [self loadImage:imgUrl1];
       });
       
       dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           image2 = [self loadImage:imgUrl2];
       });
       
       dispatch_group_notify(group, dispatch_get_main_queue(), ^{
           self.imageview1.image = image1;
           self.imageView2.image = image2;
       });
    });
    

    7.1中等到两张图片加载完成后一起刷新,7.2就是典型的异步并行的例子,不需要理会各自图片加载的先后问题,完成加载图片刷新UI即可。从加载图片中来说,第7.1种不太合适使用,但是对于在上边场景选择中的创作工具来说有很大的好处,首先得异步进行,然后异步中有得按顺序执行几个任务,比如上传章节内容。因此,我们可以灵活考虑使用这两多线程任务执行方式,实现各种场景。

    相关文章

      网友评论

        本文标题:问题:请简单介绍下iOS的多线程

        本文链接:https://www.haomeiwen.com/subject/ojuqohtx.html