美文网首页
iOS开发 多线程的运用

iOS开发 多线程的运用

作者: 弹一首键盘协奏曲 | 来源:发表于2020-02-20 16:27 被阅读0次

    在iOS开发上搬了几年砖了,一直在向各位大神学习,这段时间公司项目完工了,整理一下相关技术点,向后来者做个借鉴,沉淀一下自己。

    说起多线程基本上每个开发者都会用到,今天总结一下多线程方面的应用技巧。

    总纲

    1.概念相关

    2.应用场景

    3.相关技术和用法

    1.概念相关

    1.1、什么是多线程?

    线程是指程序在执行过程中能够执行代码的一个执行单元,线程主要有新建、就绪、挂起、结束四种状态。

    1.2、线程与进程的区别?

    程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序过程就称之为进程。进程是程序执行的一次活动。进程中有线程在执行,且一个进程中最少有一条线程,这条线程我们叫它为主线程(主线程中运行着runloop),主线程一般显示、刷新UI界面,处理UI事件(点击、滚动、拖拽事件等),我们也一般叫它UI线程。当然进程也也可以开多条线程,我们称之为子线程。开多条线程的使用情况我们称为多线程。

    1.3、多线程的原理

    同一时间内一个CPU只能处理一条线程,也只有一条线程在工作,在单核cpu情况下多线程其实就是一个cpu在多条线程之间调度,当然多核CPU才是真正意义上的多线程。CPU在多条线程调度会消耗大量的CPU资源,每条线程被调度频次会降低且消耗内存,一般情况不要同时开5条线程以上

    1.4、多线程优缺点

    优点:适当提高了程序的执行效率,把那些占用时长的操作放到后台处理,优化应用流畅度,增强用户体验。

    缺点:大量的线程需要更多的内存。当多个线程对同一资源出现争夺时候还要注意线程安全问题

    2.应用场景

    一般情况下我们使用一条线程进程就能跑通了,那么既然有多线程,我们在什么情况下使用多线程呢?我们都知道主线程是UI线程, 它可以渲染UI,负责将视图呈现给我们,当这个时候如果有一个非常耗时的操作,比如说视频的加载或者图片的下载,如果网络不好的情况下可能要等好久,如果在一条线程中执行,那么我们的应用界面将出现卡死现象。这时候如果把图片下载放到子线程中,等图片下载完成再回到主线程渲染UI,那么就不会出现卡死或者卡顿现象。开发中常见的使用多线程地方有图片的下载、版本号的加载、消息红点的校对、计时器、文件的下载等

    3.技术方案

    目前在iOS开发中有四种技术方案,分别是Pthread、NSThread、GCD、NSOperation

    3.1、Pthread

    Pthread是一套通用的API,可以跨平台使用,但是使用难度大,语言是C语言,线程生命周期由程序员管理,在开发中几乎不用,当然在平常项目开发中我也没用过,只在闲暇时候demo写过简单的使用方法。

    //使用时需要引入#import <pthread.h>
          //创建线程对象
            pthread_t thread = NULL;
            
            //传递的参数
            id str = @"param";
    
            //创建线程
            /* 参数一:线程对象 传递线程对象的地址
               参数二:线程属性 包括线程的优先级等
               参数三:子线程需要执行的方法
               参数四:需要传递的参数
             */
            int result = pthread_create(&thread, NULL, func, (__bridge void *)(str));
            if (result == 0) {
                NSLog(@"线程创建成功了^_^");
            } else {
                NSLog(@"创建线程失败 ^o^。创建结果是:%d", result);
            }
            //手动把当前线程结束掉
            // pthread_detach:设置子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
            pthread_detach(thread);
    
    //下面是传递的执行方法
    void * func(void *param)
    {
            //打印当前线程
            NSLog(@"当前线程是:%@",  [NSThread currentThread]);
        return NULL;
    }
    
    

    3.2、NSThread

    NSThread 是OC 面向对象,简单易用,只负责创建,不用管理线程死亡,偶尔使用
    NSThread是一个类,有三种初始化方法:

    //创建线程
       NSThread * newThread = [[NSThread alloc]initWithTarget:self selector:@selector(demo:) object:@"Thread"];
       //或者
       NSThread  * newThread =[[NSThread alloc]init];
       NSThread  * newThread = [[NSThread alloc]initWithBlock:^{
           NSLog(@"通过block创建");
       }];
    

    开启线程:

    [newThread start];
    

    暂停线程:

    [NSThread sleepForTimeInterval:1.0]; (一秒为例)
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
    

    线程取消:

    [newThread cancel];
    

    停止线程:

    [NSThread exit];
    

    设置优先级:
    iOS8之前:

    [NSThread currentThread];
    

    iOS8之后:

    //通过qualityOfService枚举的方式设置优先级
    NSQualityOfServiceUserInteractive:最高优先级,用于用户交互事件
    
    NSQualityOfServiceUserInitiated:次高优先级,用于用户需要马上执行的事件
    
    NSQualityOfServiceDefault:默认优先级,主线程和没有设置优先级的线程都默认为这个优先级
    
    NSQualityOfServiceUtility:普通优先级,用于普通任务
    
    NSQualityOfServiceBackground:最低优先级,用于不重要的任务
    
    
    比如设置最低优先级
    [newThread setQualityOfService: NSQualityOfServiceBackground];
    
    

    设置线程名称:

    [newThread setName:@"自定义的线程名"];
    

    获取主线程

    [NSThread mainThread];
    

    检查是否为主线程:

    [thread isMainThread];
    

    线程之间通讯

    //指定当前线程操作
    [self performSelector:@selector(func)];
    [self performSelector:@selector(func) withObject:nil];
    [self performSelector:@selector(func) withObject:nil afterDelay:2.0];
    
    //子线程回到主线程操作,比如在主线程刷新UI
    [self performSelectorOnMainThread:@selector(func) withObject:nil 
    waitUntilDone:YES];
    
    //主线程指定其他线程执行操作
    [self performSelector:@selector(func) onThread:newThread 
    withObject:nil waitUntilDone:YES]; 
    //这里指定为后台线程
    [self performSelectorInBackground:@selector(func) withObject:nil];
    

    线程同步:
    在程序运行中,因为资源在内存中是共享的,如果存在多条线程,那么各个线程读写资源就会存在先后顺序或者同时读写,因此会出现读写混乱或者错误。为了防止这样的事情发生我们要在读写数据时加锁,这样在操作同一个数据时候线程只有一个,一个操作完成后另一个才能操作。但上锁就会加大CPU开销,造成性能降低。
    iOS中NSLock / NSConditionLock / NSRecursiveLock / @synchronized都可以实现线程上锁的操作。具体用法很简单,随便搜索都一大把结果这里就不一一说明了。

    3.3、 GCD

    GCD(Grand Central Dispatch)是iOS4引入的强大的线程处理技术,它是基于XNU内核开发的,性能极为优越。
    这里有我以前写的一篇GCD应用可以移步去看一下详细应用。

    3.4、NSOperation

    NSOperation 是对GCD的封装,NSOperation是一种抽象类,完全面向对象化,使用的时候要使用其子类调取方法或者继承它自定义具体的操作。相比于GCD简单易用,代码可读性高。
    NSOperation有两个子类NSBlockOperation和NSInvocationOperation,使用区别是一个是block回调,一个是NSInvocation回调

    3.4.1、 NSInvocationOperation子类

    //创建操作对象,封装执行的任务
    NSInvocationOperation *operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(test) object:nil];
         
        //执行操作
        [operation start];
    

    执行操作就会调用target的test方法,不过操作对象时候默认在主线程中执行,只有添加到队列中才会开启新的线程。默认情况下如果没放到队列Queue中都是同步执行的,只有将NSInvocationOperation 对象放到NSOperationQueue中才会开启线程异步执行。

    3.4.2、NSBlockOperation子类

    //创建NSBlockOperation操作对象
         NSBlockOperation *operation=[NSBlockOperation blockOperationWithBlock:^{
              //执行的操作1
          }];
          
         //和NSInvocationOperation不同的是NSBlockOperation有一个添加操作的方法
         [operation addExecutionBlock:^{
              //执行的操作2
          }];
         [operation addExecutionBlock:^{
              //执行的操作3
          }];
         [operation addExecutionBlock:^{
              //执行的操作4
          }];
    //开启执行操作    
         [operation start];
    

    需要注意的是只要NSBlockOperation封装的操作数大于1,就会进行异步执行操作
    3.4.3、NSOperationQueue
    NSOperation可以通过start方法来执行任务,但默认是同步执行,除非NSBlockOperation封装的操作数大于1的时候才会异步执行。但是如果把NSOperation对象添加到NSOperationQueue(队列)中,系统会自动异步执行NSOperation中的操作任务,添加操作任务到NSOperationQueue中自动开启线程执行任务。

        //创建NSOperationQueue
          NSOperationQueue * queue=[[NSOperationQueue alloc]init];
          //把操作添加到队列中
         //第一种方式
          [queue addOperation:operation1];
          [queue addOperation:operation2];
          [queue addOperation:operation3];
          //第二种方式
          [queue addOperationWithBlock:^{
             //直接添加操作
         }];
        // 第三种直接添加NSOperation数组,不过如果Bool类型的wait为yes的话会阻塞当运行前线程直到队列全部添加完成。如果为no就相当于批量添加到队列中。
          [queue addOperatios:(NSArray<NSOperation *> *)ops  waitUntilFinished:(BOOL)wait];
    

    相关文章

      网友评论

          本文标题:iOS开发 多线程的运用

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