美文网首页iOS面试
iOS之多线程(待续)

iOS之多线程(待续)

作者: SK丿希望 | 来源:发表于2017-10-26 16:27 被阅读19次

    什么是多线程?

    说白了就是CPU快速的在多条线程之间调度(即切换),且多条线程可以并发执行(即同时执行)
    提到多线程,不得不提一下进程

    那什么是进程了?

    可以理解为在系统中正在运行的程序,每个进程之间是独立的,而且每个进程均运行在其专有且受保护的内存空间.

    什么线程?

    独立执行的代码段,一个线程同时间只能执行一个任务,反之多线程并发就可以在同一时间执行多个任务。

    那进程和线程有什么关系了?

    其实一个进程想执行任务,那么他就需要线程,因为进程中的所有任务都是在线程中执行的,故每个进程至少拥有一条线程(即我们的主线程)

    在说下主线程吧?

    又称UI线程,其实就是一个iOS程序运行后,默认开启的第一条线程,我们称为主线程,作用主要是
    1.处理UI事件(比如点击事件,滚动事件,拖拽事件等等),
    2.刷新UI

    那再来说说多线程的意义吧?(个人理解)

    举个例子,就像我们听歌一样,如果我在同时听歌的时候又想下载歌曲,如果只有一条线程的话,我们知道一条线程同一时间只能执行一个任务,为了满足这个需求,多线程的意义在此,所谓的多线程主要是就是CPU快速在多条线程之间调度,(并发执行)

    那么多线程有哪些实现方案了?

    1.pthread (C语言 是一套通用的多线程API 线程的生命周期是需要程序员手动管理的)
    2.NSThread (OC 使用更加面向对象 可以直接操作线程对象 但是线程的生命周期也是需要程序员手动管理的)

    • 优点:轻量级,简单易用,可以直接操作线程对象
    • 缺点: 需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。

    3.GCD (C语言 充分利用设备的多核 生命周期自动管理)

    • 优点:是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。
    • 缺点: 使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。

    4.NSOperation (OC 基于GCD的 使用更加面向对象 线程生命周期自动管理)

    • 优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD 的封装,比GCD更加面向对象
    • 缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation.

    多线程的优缺点?

    有利必有弊嘛没有十全十美的,当然多线程也不例外

    优点:

    1.能够适当的提高程序的执行效率
    2.适当的提高资源利用率(CPU,内存利用率)

    缺点:

    1.如果大量的开启线程,会降低程序的性能
    2.线程越多,那么CPU的工作量越大
    3.程序的设计就更加复杂(例如线程之间的通信,多线程的数据共享)(推荐3到5条)

    NSThread(个人用的比较少)

    一个NSThread对象就代表一条线程,生命周期需要手动管理


    Snip20171026_1.png

    线程状态有

    1.新建New (进入就绪状态->运行状态.当线程任务执行完毕,自动进入死亡状态-> - (void)start)
    2.就绪Runnable
    3.强制停止线程
    4.运行状态 Running
    5.阻塞 Blocked
    6.死亡 (一旦线程停止(死亡)了,就不能再次开启任务了)


    Snip20171026_2.png

    互斥锁

    1.使用格式:@synchronized(锁对象) // 需要锁定的代码
    2.使用优缺点:
    a.优点:能防止因为多线程抢夺资源造成的数据安全问题.
    b.缺点:需要消耗大量的CPU资源
    3.使用原理:就是使用了线程同步技术,多条线程在同一条上执行(按顺序的执行任务)


    NSOperation

    首先NSOperation是一个抽象类,并不具备封装操作的能力,必须使用NSOperation子类的方式有3种:
    NSInvocationOperation
    NSBlockOperation
    自定义子类继承NSOperation,实现内部相应的方法


    Paste_Image.png
    实现步骤

    1.先将要执行的操作封装到一个NSOperation对象中
    2.然后将NSOpration对象添加到NSOprationQueue(队列)中
    3.系统会自动将NSOprationQueue中的NSOpration取出来
    4.将取出来的NSOpration封装的操作放到一条新线程中执行

    那NSOprationQueue又是什么了?

    我们称之为队列,只要是自己创建的队列,就会在子线程中执行,而且默认是并发的,且队列可以取消,暂停,恢复,但是这些操作只会对后面未执行的任务进行操作,不会影响当前正在执行的,且取消不可恢复
    自己创建: alloc/init --> 默认是并发 --> 也可以让它串行
    主队列 : mainQueue

    Paste_Image.png
    NSOperationQueue的作用

    NSOperation可以调用start方法来执行任务,但默认是同步执行的
    如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
    添加操作到NSOperationQueue中

    - (void)addOperation:(NSOperation *)op;
    - (void)addOperationWithBlock:(void (^)(void))block
    
    什么是并发数

    同时执行的任务数
    比如,同时开3个线程执行3个任务,并发数就是3
    最大并发数的相关方法

    - (NSInteger)maxConcurrentOperationCount;
    - (void)setMaxConcurrentOperationCount:(NSInteger)cnt;
    
    队列的取消、暂停、恢复

    取消队列的所有操作

    - (void)cancelAllOperations;
    

    提示:也可以调用NSOperation的- (void)cancel方法取消单个操作
    暂停和恢复队列

    - (void)setSuspended:(BOOL)b; // YES代表暂停队列,NO代表恢复队列
    - (BOOL)isSuspended;
    
    依赖

    NSOperation之间可以设置依赖来保证执行顺序
    设置依赖来保证执行顺序,可以设置不同队列中的opration创建的依赖关系

    [operationB addDependency:operationA]; // 操作B依赖于操作A
    
    使用,使用两个图片拼接这种典型
    #import "ViewController.h"
    @interface ViewController ()
    @property (weak, nonatomic) IBOutlet UIImageView *imageView;
    @end
    @implementation ViewController
    - (void)viewDidLoad {
     [super viewDidLoad];
    }
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
     [self setUp];
    }
    - (void)setUp
    {
     __block UIImage *image1=nil;
     __block UIImage *image2=nil;
     //创建一个异步队列
     NSOperationQueue *queue=[[NSOperationQueue alloc]init];
    
     //开启一个子线程下载图片
     NSOperation *op1=[NSBlockOperation blockOperationWithBlock:^{
    
         NSURL *url=[NSURL URLWithString:@"http://s.wasu.cn/data/images/201604/13/570e0b67c15fb.jpg"];
    
         NSData *date=[NSData dataWithContentsOfURL:url];
    
         image1=[UIImage imageWithData:date];
    
         NSLog(@"---op1----%@",[NSThread currentThread]);
    
     }];
     //下载另一个图片
     NSOperation *op2=[NSBlockOperation blockOperationWithBlock:^{
    
         NSURL *url=[NSURL URLWithString:@"http://i1.hdslb.com/bfs/archive/5e91addadc849572575b592814846977ab5559f1.jpg"];
         NSData *date=[NSData dataWithContentsOfURL:url];
         image2=[UIImage imageWithData:date];
         NSLog(@"---op2----%@",[NSThread currentThread]);
    
     }];
    
     //拼接图片
     NSOperation *op3=[NSBlockOperation blockOperationWithBlock:^{
    
         CGSize size=CGSizeMake(300, 400);
    
         UIGraphicsBeginImageContext(size);
    
         [image1 drawAsPatternInRect:CGRectMake(0, 0, 150,400)];
    
         [image2 drawAsPatternInRect:CGRectMake(150, 0, 150, 400)];
    
         UIImage * image=UIGraphicsGetImageFromCurrentImageContext();
    
         UIGraphicsEndImageContext();
    
         [[NSOperationQueue mainQueue]addOperationWithBlock:^{
             NSLog(@"进入主线程");
             self.imageView.image=image;
         }];
         NSLog(@"---op3----%@",[NSThread currentThread]);
    
     }];
    
     op1.completionBlock=^{
    
         NSLog(@"第一张图下载完毕");
    
     };
    
     op2.completionBlock=^{
    
         NSLog(@"第二张图下载完毕");
     };
    
     //依赖要放在添加队列之前
     [op3 addDependency:op1];
     [op3 addDependency:op2];
    
     NSArray *opreations=[NSArray arrayWithObjects:op1,op2,op3, nil];
    
     [queue addOperations:opreations waitUntilFinished:NO];
    }
    @end
    output:
    2016-06-27 17:35:39.651 图片合成[10336:2417737] ---op2----<NSThread: 0x7fe702c4c4e0>{number = 3, name = (null)}
    2016-06-27 17:35:39.651 图片合成[10336:2417736] 第二张图下载完毕
    2016-06-27 17:35:40.528 图片合成[10336:2417738] ---op1----<NSThread: 0x7fe702e80fa0>{number = 4, name = (null)}
    2016-06-27 17:35:40.529 图片合成[10336:2417781] 第一张图下载完毕
    2016-06-27 17:35:40.552 图片合成[10336:2417666] 进入主线程
    2016-06-27 17:35:40.552 图片合成[10336:2417737] ---op3----<NSThread: 0x7fe702c4c4e0>{number = 3, name = (null)}
    
    操作的监听

    可以监听一个操作的执行完毕

    - (void (^)(void))completionBlock;
    - (void)setCompletionBlock:(void (^)(void))block;
    
    自定义NSOperation
    自定义NSOperation的步骤:

    重写- (void)main方法,在里面实现想执行的任务

    重写- (void)main方法的注意点

    自己创建自动释放池(因为如果是异步操作,无法访问主线程的自动释放池)
    经常通过- (BOOL)isCancelled方法检测操作是否被取消,对取消做出响应


    GCD

    Paste_Image.png
    • GCD的2个核心:任务,队列
    • 任务:即为在block中执行的代码
    • 队列:用来存放任务的
    • 注意事项:队列!=线程.队列中存放的任务最后都要由线程来执行.队列的原则:先进先出,后进后出(FIFO)
    Paste_Image.png
    队列
    • 队列分4种
      -- 串行队列:任务一个一个的执行
      -- 并发队列:任务并发执行
      -- 主队列:根主线程相关的队列,主队列的任务是在主线程中执行的
      -- 全局队列:一个特殊的并发队列
    Paste_Image.png
    • 并发队列与全局队列的区别
      -- 并发队列有名称,可以跟踪错误.全局队列没有
      -- 在ARC中2个队列不需要考虑内存释放,但是在MRC中并发队列创建出来需要release操作
      -- 一般开发过程中我们使用全局队列
    • 同步和异步的区别
      -- 同步:(dispatch_sync)只能子安当前线程中执行任务,不具备开启新线程的能力
      -- 异步:(dispatch_async)可以在新的线程中执行任务,具备开启线程的能力
    • 各个队列的执行效果
      -- 同步串行队列:即在当前线程中顺序执行
      -- 异步串行队列:开辟一条新线程,在该线程中顺序执行
      -- 同步并发队列:不开辟新线程,在当前线程中顺序执行
      -- 异步并发队列:开辟多条线程,并且线程会重用,无序执行
      -- 异步主队列:不开辟新线程,顺序执行
      -- 同步主队列:会造成死锁(主线程与主队列相互等待,卡住主线程)
    • 线程间的通讯:(经典案例)子线程进行耗时操作(例下载更新等),回主线程刷新UI
     dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
    // 处理耗时操作的代码块... 
    dispatch_async(dispatch_get_main_queue(), ^{  // 通知主线程刷新 
    //回调或者说是通知主线程刷新, 
      }); 
    });  
    
    Paste_Image.png

    相关文章

      网友评论

        本文标题:iOS之多线程(待续)

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