iOS开发之多线程

作者: hmj1993 | 来源:发表于2018-05-05 11:27 被阅读58次

    进程

    1.进程是指在系统中正在运行的一个应用程序
    2.每个进程之间是相互独立的,每个进程均运行在其专用且受保护的内存空间中

    线程

    1.一个进程要想执行任务,必须得有线程(每一个进程至少要有一条线程)
    2.一个进程(程序)的所有任务都在线程中执行

    线程的串行

    1.一个线程中任务的执行是串行的
    2.如果要在一个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
    3.也就是说,在同一时间内,一个线程只能执行一个任务
    4.因此,也可以认为线程是进程中的一条执行路径

    进程和线程的比较

    1.线程是CPU调用(执行任务)的最小单位
    2.进程是CPU分配资源和调度的单位
    3.一个程序可以对应多个进程,一个进程中可以有多个线程,但至少要有一个线程
    4.同一个进程内的线程共享进程的资源

    多线程

    1.一个进程中可以开启多条线程,每个线程可以并发(同时)执行不同的任务
    2.多线程可以提高任务的执行效率

    多线程的原理

    1.同一时间,CPU只能处理一条线程,只有一条线程在执行(单核)
    2.多线程并发(同时)执行,其实是CPU快速的在多条线程之间调度(切换)
    3.如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象
    4.如果线程非常多,会导致CPU在很多的线程之间调度,消耗大量的CPU资源,每条线程被调度执行的频次会降低(线程的执行效率降低)

    多线程的优缺点

    • 优点:
      1.能适当提高程序的执行效率
      2.能适当提高资源利用率(CPU,内存利用率)
    • 缺点:
      1.创建线程是有开销的,iOS下主要成本包括:内核数据结构(大约1kb),栈空间(子线程512kb,主线程1mb,也可以使用-setStackSize:设置,但必须是4k的倍数,而且最小是16k),创建线程大约需要90毫秒的创建时间
      2.如果开启大量的线程,会降低程序的性能
      3.线程越多,CPU在调度线程上的开销就越大
      4.程序设计更加复杂,比如线程之间的通信,多线程的数据共享

    多线程在iOS开发中的应用

    • 主线程:
      一个iOS程序运行后,默认会开启一条线程,称为主线程或UI线程

    • 主线程的主要作用:
      1.显示/刷新UI界面
      2.处理UI事件(比如点击事件,滚动事件,拖拽事件等)

    • 主线程的使用注意:
      1.不要将比较耗时的操作放到主线程中
      2.耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验

    • 耗时操作的执行
      将耗时操作放在子线程(后台线程,非主线程)

    • 获得主线程

      NSThread *main=[NSThread mainThread];

    • 获得当前线程

      NSThread *current=[NSThread currentThread];

    • 判断是否是主线程
      1.number==1?
      2.类方法
      BOOL isMain= [NSThread isMainThread];
      3.对象方法
      BOOL ifMain=[current isMainThread];

    • iOS中多线程的实现方案
    技术方案 简介 语言 线程生命周期 使用频率
    pthread 一套通用的多线程API;
    适用于unix/linux/windows等系统;
    跨平台/可移植;
    使用难度大
    C 程序员管理 几乎不用
    NSThread 使用更加面向对象;
    简单易用,可直接操作线程对象
    OC 程序员管理 偶尔使用
    GCD 旨在替代NSThread 等线程技术;
    充分利用设备的多核
    C 自动管理 经常使用
    NSOperation 基于GCD(底层是GCD);
    比GCD 多了一些更简单实用的功能;
    使用更加面向对象
    OC 自动管理 经常使用

    pthread的简单使用

    1.#import <pthread.h>
    2. //创建线程对象
        pthread_t thread;
        //创建线程
        /*
         第一个参数:线程对象 传递地址
         第二个参数:线程的属性 可设为null
         第三个参数:指向函数的指针
         第四个参数:函数需要接受的参数
         */
        pthread_create(&thread, NULL, test, NULL);
    
    void *test(void * param){ 
        return NULL;
    }
    

    NSThread的基本使用

    • 方法一
        /*
         第一个参数:目标对象
         第二个参数:方法选择器 调用的方法
         第三个参数:前面调用方法需要传递的参数 可以为nil
         */
        NSThread *subthread=[[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"123"];
        //设置线程的名字
        subthread.name=@"subthread";
        //优先级  0.0(最低)--0.5(默认)--1.0( 最高)
        subthread.threadPriority=1;
        //需要手动启动线程
        [subthread start];
    
    -(void)run:(NSString *)param{
        
    }
    
    • 方法二
    //分离子线程,自动启动线程
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"分离子线程"];
    
    • 方法三
    //开启一条后台线程
    [self performSelectorInBackground:@selector(run:) withObject:@"开启一条后台线程"];
    
    • 后两种方法的优缺点
      1.优点:
      简单快捷
      2.缺点:
      没有办法拿到线程对象,无法设置优先级或其他属性

    • NSThread的生命周期
      当线程中的任务执行完毕后才被释放

    • 控制线程的状态
      1.创建线程
      init
      新建, 此时在内存中存在,但是不在可调度线程池中
      2.启动线程
      -(void)start;
      进入就绪状态,进入可调度线程池,当CPU调度时,进入运行状态,当线程任务执行完毕,自动进入死亡状态
      3.阻塞/暂停线程
      +(void)sleepUntilDate:(NSDate *)date;
      +(void)sleepForTimeInterval:(NSTimeInterval)ti;
      进入阻塞状态,推出可调度线程池,当时间到时,再次进入就绪状态,进入可调度池
      4.强制停止线程
      +(void)exit;
      进入死亡状态,一旦线程死亡了,就不能再次开启任务

    • 线程安全
      多线程的安全隐患
      1.资源共享
      一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
      比如多个线程访问同一个对象,同一个变量,同一个文件
      当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题

    • 安全隐患解决--互斥锁
      互斥锁使用格式

    @synchronized(锁对象,通常为self){
    //需要锁定的代码
    }
    

    锁:必须是全局唯一的一把锁,
    锁定一份代码只用一把锁,用多把锁是无效的
    1.注意加锁的位置
    2.注意加锁的前提条件,多线程共享同一块资源
    3.注意加锁是需要付出代价的,需要耗费性能
    4.加锁的结果:线程同步
    线程同步:多条线程在同一条线上执行(按顺序的执行任务)

    • 互斥锁的优缺点
      优点:
      能有效防止因多线程抢夺资源造成的数据安全问题
      缺点:
      需要消耗大量的CPU资源

    • 互斥锁的使用前提:
      多条线程抢夺同一块资源

    • 原子和非原子属性
      oc在定义属性时有nonatomic和atomic两种选择
      atomic:原子属性,为setter方法加锁(默认就是atomic)
      线程安全,但需要消耗大量的资源
      nonatomic:非原子属性,不会为setter方法加锁
      非线性安全,适合内存小的移动设备

    • iOS开发的建议
      1.所有属性都声明为nonatomic
      2.尽量避免多线程抢夺同一资源
      3.尽量将加锁,资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力

    • 线程间通信
      在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信

    • 线程间通信的体现
      1.一个线程传递数据给另外一个线程
      2.在一个线程中执行完特定任务后,转到另一个线程继续执行任务
      线程间通信常用方法

    -(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
    
    -(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
    

    GCD

    纯c语言,提供了非常多强大的函数

    • gcd的优势
      1.gcd是苹果公司为多核的并行运算提出的解决方案
      2.gcd会自动利用更多的cpu内核(比如双核,四核)
      3.gcd会自动管理线程的生命周期(创建线程,调度任务,销毁线程)
      4.程序员只需要告诉gcd想要执行什么任务,不需要编写任何线程管理代码

    • 任务和队列
      任务:执行什么操作
      队列:用来存放任务

    • gcd的使用就两个步骤
      1.定制任务,确定想做的事情
      2.将任务添加到队列中,gcd会自动将队列中的任务取出,放到对应的线程中执行

    • 任务的取出遵循队列的FIFO原则:先进先出

    • 执行任务
      同步:只能在当前线程中执行任务,不具备开启新线程的能力,需要立刻执行,执行完毕再执行下面的
      dispatch_sync(dispatch_queue_t queue, ^(void)block);
      异步:可以在新的线程中执行任务,具备开启新线程的能力,即使没有执行完毕,后面的也可以执行
      dispatch_async(dispatch_queue_t _Nonnull queue, ^(void)block);

    • 队列
      并发队列:可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
      并发功能只有在异步函数下才有效
      串行队列:让任务一个接着一个地执行(一个任务执行完毕后,在执行下一个任务)

    • 同步和异步主要影响:能不能开启新的线程

    • 并发和串行主要影响:任务的执行方式

    • gcd的基本使用

    //1.创建队列
        /*
         第一个参数:c语言的字符串,就是一个标识符,用来区分队列,没有实际意义
         第二个参数:队列类型
         DISPATCH_QUEUE_CONCURRENT 并行队列
         DISPATCH_QUEUE_SERIAL 串行队列
         */
       dispatch_queue_t queue= dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);//方法一
        /*
         第一个参数:队列优先级
         第二个参数:暂时无用,写0即可
         */
        dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//方法二,获得全局并发队列
        
      //2.封装任务,添加任务到队列中
        /*
         第一个参数:队列
         第二个参数:要执行的任务
         */
        dispatch_async(queue, ^{
            
        });
    
      并发队列 串行队列 主队列
    异步函数 会开启多条线程,队列中的任务是并发执行 会开启线程,开一条线程,队列中的任务是串行执行 不会开启线程,所有任务都在主线程中执行,队列中的任务是串行执行
    同步函数 不会开启线程,队列中的任务是串行执行 不会开启线程,队列中的任务是串行执行 在主线程中会产生死锁,在子线程中没有影响
    • 并发队列
      两种创建方法:
      1.自己创建
    dispatch_queue_t queue= dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
    

    2.获得全局并发队列

    dispatch_queue_t queue1=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    • 串行队列
      两种创建方法
      1.自己创建
    dispatch_queue_t queue= dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
    

    2.获得主队列(和主线程相关的队列)

    dispatch_queue_t queue1=dispatch_get_main_queue();
    
    • 主队列是gcd自带的一种特殊的串行队列

    • 放在主队列中的任务,都会放到主线程中执行

    • 主队列特点
      如果主队列发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务,直到主线程空闲位置

    • gcd实现线程间通信
      gcd同步/异步函数嵌套使用同步/异步函数

    • gcd的常用函数

    1. 延迟执行
        /*
         第一个参数:DISPATCH_TIME_NOW 从现在开始计算时间
         第二个参数:延迟的时间 gcd时间单位:纳秒
         第三个参数:队列
         */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
        });
    
    1. 一次性代码,不能放在懒加载中,主要用在单例中
       static dispatch_once_t onceToken;
       dispatch_once(&onceToken, ^{
           
       });
    
    1. gcd栅栏函数
      3.1 作用:控制多线程中并发任务的执行顺序,比如放置在任务1,任务2之后,任务3之前,则结果是等任务1,2都执行完毕后,再执行任务3
      3.2 注意:栅栏函数不能使用全局并发队列
        /*
         第一个参数:队列
         第二个参数:操作
         */
        dispatch_barrier_async(queue, ^{
            //操作
        });
    
    1. gcd快速迭代(遍历)
      for循环是同步的,gcd是开子线程和主线程一起完成遍历任务,任务的执行是并发的
        /*
         第一个参数:遍历的次数
         第二个参数:队列(只能传并发队列,串行队列无效果,主队列会死锁)
         第三个参数:index 索引
         */
        dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
            NSLog(@"%zd",index);
        });
    
    1. gcd队列组
      gcd队列组的使用
        dispatch_queue_t queue2=dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
        dispatch_group_t group=dispatch_group_create();
        /*
         作用:
         第一个:封装任务
         第二个:把任务添加到队列中
         第三个:会监听任务的执行情况,通知group
         */
        dispatch_group_async(group, queue2, ^{
            
        });
        dispatch_group_async(group, queue2, ^{
            
        });
        dispatch_group_async(group, queue2, ^{
            
        });
        //拦截通知,当队列组中所有的任务都执行完毕的时候进入到下面的方法
        //内部本身是异步的
        dispatch_group_notify(group, queue2, ^{
            
        });
    

    老式写法

        dispatch_queue_t queue3=dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
        dispatch_group_t group1=dispatch_group_create();
        //在该方法后面的异步任务会被纳入到队列组的监听范围,进入群组
        //enter和leave必须配套出现
        dispatch_group_enter(group1);
        dispatch_sync(queue3, ^{
            //离开群组
            dispatch_group_leave(group1);
        });
        
        dispatch_group_enter(group1);
        dispatch_sync(queue3, ^{
            //离开群组
            dispatch_group_leave(group1);
        });
        
        //等待 等价于dispatch_group_notify,本身是阻塞的,即我不执行,下面的也不执行
        //DISPATCH_TIME_FOREVER 表示死等,直到队列组中所有的任务都执行完毕之后才执行,
        dispatch_group_wait(group1, DISPATCH_TIME_FOREVER);
    
    

    NSOperation

    • 作用:
      配合使用NSOperation和NSOperationQueue也能实现多线程编程

    • 具体步骤:
      1.先将需要执行的操作封装到一个NSOperation对象中
      2.将NSOperation对象添加到NSOperationQueue中
      3.系统会自动将NSOperationQueue中的NSOperation取出来
      4.将取出的NSOperation封装的操作放到一条新线程中执行

    • NSOperation的子类
      NSOperation是个抽象类,并不具备封装操作的能力,必须使用他的子类

    • 使用NSOperation子类的方式有3中
      1.NSInvocationOperation

    //1.创建操作,封装任务
        /*
         第一个参数:目标对象
         第二个:调用的方法
         第三个:调用的方法的参数
         */
        NSInvocationOperation *op1=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run) object:nil];
    

    2.NSBlockOperation

    //1.创建操作,封装任务
    NSBlockOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
            //任务
        }];
    //追加任务
        //注意:如果一个操作中的任务数量大于1,那么会开子线程并发执行任务
        //注意:不一定是子线程,有可能是主线程
        [op2 addExecutionBlock:^{
            //任务
        }];
    

    3.自定义子类继承NSOperation,实现内部的main方法

    //告知要执行的任务是什么
    //有利于代码隐蔽,有益于代码复用
    -(void)main{
       //任务
    }
    

    创建好操作后

    //2.启动/执行操作
    //注意:如果直接start,那么不会创建新线程,就和一般的[self 方法名];效果一样,无法实现多线程编程
        [op1 start];
        [op2 start];
    
    //2.创建队列,这样才可能实现多线程编程
        /*
         主队列:[NSOperationQueue mainQueue]和gcd中的主队列一样,是串行队列,在主线程中执行
         非主队列:[[NSOperationQueue alloc]init]非常特殊,因为同时具备并发和串行的功能
         默认情况下,非主队列是一个并发队列
         */
        
        NSOperationQueue *queue=[[NSOperationQueue alloc]init];
        //3. 添加操作到队列中,内部已经调用了start方法
        [queue addOperation:op2];
        
        //简便方法   首先创建了NSBlockOperation操作,然后把操作添加到队列里
        [queue addOperationWithBlock:^{
            
        }];
    

    NSOperation的其他用法

    NSOperationQueue *queue1=[[NSOperationQueue alloc]init];
        //设置最大并发数:同一时间最多有多少个操作/任务可以执行
        //设置为1则为串行队列,但是不等于只开一条线程
        //不能设置为0,因为会不执行任务
        //大于1就是并发队列
        //设置为-1则代表最大值,不受限制
        queue.maxConcurrentOperationCount=5;
        //暂停 可以恢复 不能暂停当前正在执行的任务,需要等当前任务执行完毕再暂停
        //队列中的任务也是有状态的  已经执行完毕/正在执行/排队等待执行
        [queue setSuspended:YES];
        //继续
        [queue setSuspended:NO];
        //取消  不可以恢复 不能暂停当前正在执行的任务,需要等当前任务执行完毕再暂停
        //该方法内部调用了所有操作的cancel方法
        //自定义NSOperation
        gh *op3=[[gh alloc]init];
    //需要先在自定义的gh中的main方法中设置
        if (op3.isCancelled==YES) {
            return;
        }
        [queue cancelAllOperations];
    

    NSOperation操作依赖和监听

       //添加操作依赖
        //注意:不能循环依赖,不会崩溃,但是谁都不会执行  可以跨队列依赖
        [op1 addDependency:op2];
        [op2 addDependency:op3];
        
        //操作监听
        op3.completionBlock = ^{
            //操作
        };
    

    NSOperation实现线程间通信

    NSOperationQueue *queue3=[[NSOperationQueue alloc]init];
     NSBlockOperation *down=[NSBlockOperation blockOperationWithBlock:^{
            
            //操作
            
            //在主线程中更新UI
            [[NSOperationQueue mainQueue]addOperationWithBlock:^{
                //在主线程操作UI更新
            }];
        }];
        
        [queue3 addOperation:down];
    

    以上差不多就是iOS中多线程的知识点了 ,如果哪里有错误,还希望告知一下

    相关文章

      网友评论

      本文标题:iOS开发之多线程

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