美文网首页
多线程1

多线程1

作者: project_DLQ | 来源:发表于2016-10-17 18:51 被阅读0次

    常用操作

    多行注释: Command + option + /
    获取当前的时间:CACurrentMediaTime()
    后台运行程序:[self performSelectorInBackground:@selector(longOperation) withObject:nil];
    创建子线程,让longOperation方法在子线程异步执行
    target-select:

    多线程基础

    1. 空的for循环不耗时
    2. 操作栈区内存空间不耗时,因为栈区的内存空间是连续的,不需要寻址
    3. 操作常量区内存空间不耗时,但是比操作栈区空间耗时一些,寻址只做一次
    4. 操作堆区的内存空间相对于栈区和常量区是耗时的,因为堆区的内存空间不是连续的,需要寻址
    5. I/O操作也是非常耗时的
    6. 耗时操作对UI影响:会卡死UI
    7. 如何解决耗时操作卡死UI的问题:使用多线程技术
    8. 多线程的核心思想: 就是把耗时操作放在后台执行,避免耗时操作卡死UI
    9. 在实际开发中,网络操作时非常耗时的,一般会把网络操作(下载,上传...)放在后台执行
    10. 学习多线程就是为了学习网络做准备的
      I/O操作:是把内存的数据输出到外接设备(屏幕,磁盘)output,把外接设备的数据输入到内存input

    同步&异步

    同步异步 是任务执行的两种方式
    同步:多个任务按序依次执行
    异步:多个任务同时执行,就是异步执行(后台执行就是异步执行)

    进程&线程

    进程:
    在系统中 正在运行 的一个应用程序就是一个进程
    通过 活动监视器 可以查看MAC系统中 正在运行 的所有应用程序
    每个进程之间都是 独立 的,均运行在其 专用受保护 的内存空间
    两个进程之间是无法通信的,迅雷无法帮助我们下载正在播放的音乐
    进程可以类比成正在 正常运行 的公司
    线程:
    线程可以类比成公司中的员工
    进程要想执行任务,必须要有线程,且每个进程至少有一个线程
    线程是进程的 基本执行单元,进程中的所有任务都在线程中执行
    程序启动(进程开启)会默认开启一条线程
    1个进程中可以有多个线程
    多线程:
    一个进程中可以开启多条线程,多条线程可以同时执行不同的任务
    进程-公司,线程-员工
    多线程可以解决程序阻塞的问题
    多线程可以提高程序的执行效率,给用户良好的使用体验
    多线程执行原理:
    单核CPU同一时间,CPU只能处理1个线程,只有1个线程在执行任务
    多线程的同时执行:其实就是CPU在多线程之间快速切换(调度任务)
    如果CPU调度线程的速度足够快,就造成了多线程同时 执行 的假象
    如果线程非常多,CPU会在多线线程之间不断的调度任务,结果就是消耗了大量的CPU资源,CPU会趴下
    每个线程调度的频率会降低
    线程的执行效率会下降
    多线程的优缺点
    实际开发中,能不用多线程就不用,主线程够用
    如果必须使用,就简单使用
    优点

    1. 能够适当提高程序的执行效率
    2. 能适当提高CPU和内存的利用率
    3. 线程上的任务执行完成后,线程会自动销毁,节省内存

    缺点

    1. 开启线程需要占用一定的内存空间,如果开启的线程过多,会占用大量的CPU资源,降低程序的性能
      2.占用内存空间:默认情况下,子线程512K,主线程1M,PS:IOS8中,主线程521K
    2. 线程越多,CPU调度线程的开销就越大
      时间开销
      空间开销
    3. 程序设计更加复杂:比如多线程之间的通信,多线程的数据共享

    主线程:

    1. 程序一起动就会创建主线程,主线程会执行main函数,
    2. 一个程序运行后,默认会开启1个线程,称为主线程UI线程
    3. 主线程一般用来刷新UI界面,处理UI事件
    4. 处理UI事件:点击 滚动 拖拽
    5. 主线程使用注意:
      别将耗时的操作放在主线程中
      耗时操作会卡主主线程,严重影响UI的流畅度,给用户一种卡的坏体验,影响UI交互质量

    凡是跟UI相关的都是在主线程执行的(子线程创建就有,不创建就没有)

    pthread

    pthread创建线程

    /*
         创建一个线程
         
          参数1:子线程的ID(标识)
         在C语言中,一般带`_t`/`_ref`标识数据类型
          参数2:子线程的一个属性,一般传入NULL
                NULL:表示空地址,一般在C语言中使用
                nil:表示空对象,一般在OC中使用
                其实,NULL和nil本质上没有半点区别
          参数3:子线程需要执行的函数
            void *(*)(void *)表示指向函数的指针,即函数名:函数名就是表示函数地址;数组地址就是数组名或者数组第0个角标元素的地址
            void*:表示可以指向任何地址的指针,代表任意数据类型,类似于OC的id
         void *   (*)  (void *):
         返回值   函数名   函数参数
          参数4:子线程需要执行的函数的参数
         返回值:int;在很多C语言框架中,不是遵守非零即真,因为成功的结果只有一个,0是唯一的;失败的原因有很多
         线程调试:查看方法执行的线程是否是主线程或者子线程
         {number = 1, name = main}:表示主线程
         {number = 3, name = (null)}:表示子线程,主要number != 1,就表示子线程
         提示:千万不要纠结number到底等于几,系统分配的
        __bridge:在c语言和oc语言混合开发时,需要做数据类型转换,有时候需要使用桥接
         桥接作用:在做数据类型转换时,告诉编译器如何管理c语言的内存
         提示:在ARC环境下,编译器在编译时,不会自动管理C语言申请的内存空间
         提示:加上__bridge 表示告诉编译器,c语言申请的内存也是自动管理的,因为大环境是ARC的
         MRC环境下,不需要使用__bridge,因为手动管理内存
         */
         NSLog(@"%@",[NSThread currentThread]);
        //参数1
        pthread_t ID;
        //参数4
        NSString *str = @"hello";
        //创建了一个子线程
       int result =  pthread_create(&ID, NULL, demo, (__bridge void *)(str));
        //判断子线程创建是否成功
        if(result == 0){
            NSLog(@"创建子线程成功");
        }else{
            NSLog(@"创建子线程失败");
        }
        
    
    
    /**
     子线程执行的函数
    
     */
    void *demo(void *param){
        
        NSString *str = (__bridge NSString *)(param);
        //currentThread:查看当前线程
        NSLog(@"demo %@ - %@",[NSThread currentThread],str);
        return NULL;
    }
    
    

    NSThread

    创建线程的3种方式

    //分类方法创建子线程
    //不可以拿到线程对象
    //不需要手动启动线程
    -(void)threadDemo3{
        //方便所有继承自NSObject的对象,可以直接调用线程的方法(swift里面没有这个分类,也没有GCD)
        [self performSelectorInBackground:@selector(demo:) withObject:@"perform"];
    }
    //构造方法创建子线程
    //不可以拿到线程对象
    //不需要手动启动线程
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        [self threadDemo2];
    }
    -(void)threadDemo2{
        [NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"detach"];
    }
    //构造方法创建子线程
    //可以拿到线程对象
    //需要手动启动线程
    -(void)threadDemo1{
        //创建线程对象
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"alloc"];
        //启动线程
        [thread start];
    }
    -(void)demo:(id)param{
        //子线程执行的方法
        NSLog(@"%@-%@",param,[NSThread currentThread]);
    }
    
    

    target和selector的关系

    • 执行哪个对象的哪个方法
    • 需求:执行Person对象的run方法
    NSThread *thread = [[NSThread alloc] initWithTarget:_p selector:@selector(run:) object:@"alloc"];
    

    线程生命周期/线程状态

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        NSLog(@"喜大普奔,神舟十一");
       
        //注意:千万不要在主线中,使用这个方法,会使主线程死亡
         //[NSThread exit];
        [self threadDemo];
    }
    -(void)threadDemo{
        //提示:程序员只能够做新建和就绪,其他的都由系统来处理
        
        //创建线程对象:新建状态
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
        //启动线程:就绪状态(把线程对象添加到可调度线程池,等待被CPU调度执行)
        [thread start];
        
    }
    -(void)demo{
        //NSLog(@"%@",[NSThread currentThread]);
        for (NSInteger i = 0; i < 5; i++) {
            //线程每次执行到这里就休眠1秒钟:for循环,每循环一次就休息1秒钟
            //sleepForTimeInterval:使当前的线程休眠到指定时长
            //sleepForTimeInterval:使用场景就是模拟网络延迟操作,仅仅是模拟,开发中不会使用的
            [NSThread sleepForTimeInterval:1.0];
            
            NSLog(@"%zd - %@",i,[NSThread currentThread]);
            if(i == 2){
                //sleepUntilDate:使当前线程休眠到指定日期
                [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
                NSLog(@"蓝瘦,香菇");
            }
            if(i == 3){
                //使当前线程强制死亡
                [NSThread exit];
            }
        }
    }
    

    线程属性

    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        NSLog(@"主:%tu",[NSThread currentThread].stackSize/1024);
    
        //新建线程
       NSThread *thread =  [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
        //设置线程对象的name属性,标识一个唯一的线程对象,方便跟踪
        thread.name = @"t1";
        //设置线程对象的优先级:浮点数0.0-1.0,最高是1.0,默认是0.5
        //线程优先级不能决定线程执行的先后顺序,只能决定某个线程有更多机会被CPU先调度执行完,概率事件
        //注意:实际开发中,千万不要设置优先级,或者服务器质量,会出现意想不到的问题;使用默认的,让系统自己来处理
        //thread.threadPriority = 1.0;
        //threadPriority:在目前即将被废弃,使用qualityOfService替代
        thread.qualityOfService = NSQualityOfServiceUserInteractive;
        
        
        //就绪状态
        [thread start];
        //新建线程
        NSThread *thread2 =  [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
        thread2.name = @"t2";
        thread2.threadPriority = 0.1;
        //就绪状态
        [thread2 start];
    }
    -(void)demo{
        //stackSize:线程占用内存空间大小
        NSLog(@"子:%tu",[NSThread currentThread].stackSize/1024);
        
        
        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"%zd -- %@",i,[NSThread currentThread]);
        }
        /*模拟崩溃:演示name属性
        NSMutableArray *arrM = [NSMutableArray array];
        NSObject *obj = nil;
        [arrM addObject:obj];
         */
        
        
    }
    

    资源共享-线程安全

    共享资源
    资源:一个全局对象,一个全局变量,一个文件
    共享:可以被多个对象访问
    共享资源:可以被多个对象访问的资源,比如全局对象,变量,文件
    在多线程的环境下,共享的资源,可能被多个线程共享,也就是多个线程可能会操作同一块资源

    @interface ViewController ()
    
    //总票数:共享资源
    @property (assign,nonatomic) NSInteger tickets;
    
    @end
    //需求驱动开发:没有需求,无从开发
    //需求:开发买票系统
    //先要有开发逻辑,后有开发代码
    //开发逻辑:先干什么,后干什么
    //分析需求,得到发的逻辑
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //初始化总票数
        self.tickets = 20;
        
        
    }
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        //[self sellTickers];
        //售票口1
       NSThread *thread1 =  [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
        thread1.name = @"t1";
        [thread1 start];
        //售票口1
        NSThread * thread2 =  [[NSThread alloc] initWithTarget:self selector:@selector(sellTickers) object:nil];
        thread2.name = @"t2";
        [thread2 start];
    }
    //卖票主方法
    -(void)sellTickers{
        
        while (YES) {
            //互斥锁/同步锁:使用了线程同步技术
            //特点:可以保证被锁定的代码,同一时间只有一个线程可以访问
            //self:表示互斥锁的参数,互斥锁的参数,又叫做锁对象
            //锁对象:任何继承自NSObject的对象,都可以作为互斥锁的参数,内部有把锁,默认是开启的
            //锁对象必须是全局的对象;self是最方便获取的全局的锁对象
            //局部锁对象是锁不住的,因为每次线程进来之前会新建一把锁
            //提示:加锁的事情,不能再客户端操作;是服务器加锁的,多线程资源共享,绝大多数是在服务器发生;
            //提示:加锁是牺牲了性能,保证了安全,客户端的性能不能轻易牺牲
            
            //创建一个局部的锁对象
            NSObject *obj = [[NSObject alloc] init];
            @synchronized (self) {
                //判断是否有余票
                if(self.tickets > 0 ){
                    //模拟网络延迟:没有实际意义,仅仅是模拟延迟而已,可以忽略
                    //[NSThread sleepForTimeInterval:1.0];
                    //如果有余票,卖一张
                    self.tickets = self.tickets - 1;
                    //提醒余票
                    NSLog(@"%zd--%@",self.tickets,[NSThread currentThread]);
                    
                }else{
                    //如果没有余票,提醒用户无票
                    NSLog(@"无票了");
                    break;
                }
    
            }
        }
        
    }
    
    

    原子属性

    @interface ViewController ()
    
    //非原子属性
    @property (nonatomic,strong) NSObject *obj1;
    //原子属性
    @property (strong) NSObject *obj2;//一旦重写了getter和setter方法,系统不会自动生成带下划线的成员变量
    //需要自己合成带下划线的成员变量
    
    @end
    /*
     原子属性:单写多读
     单写多读:同一时间只有一个线程可以访问setter方法,但是可以有多个线程访问getter方法
     注意:原子属性的setter方法是线程安全的,getter方法是线程非安全的
     setter方法内部有自旋锁
     自旋锁:看不见的,由系统封装的
        可以保证被锁定的代码,同一时间只能有一个线程可以访问
     一旦外面的线程,发现代码被自旋锁锁定,外面的线程会以死循环的方式等待开锁
     互斥锁:
        可以保证锁定的代码,同一时间只能有一个线程可以访问
        一旦外面的线程,发现代码被互斥锁锁定,外面的线程就会进入就绪状态,
     */
    
    @implementation ViewController
    
    @synthesize obj2 = _obj2;
    -(void)setObj2:(NSObject *)obj2{
        //由于自旋锁看不见,所以可以使用互斥锁替换,演示单写多读
        @synchronized (self) {
             _obj2 = obj2;
        }
       
    }
    -(NSObject *)obj2{
        return _obj2;
    }
    
    

    异步下载网络图片

    @interface ViewController ()
    
    //根视图
    @property (strong,nonatomic) UIScrollView *scrollView;
    
    //图片子视图
    @property (nonatomic,weak) UIImageView *imgView;
    
    @end
    //需求:异步下载网络图片,图片可以滚动,滚动视图要是根视图
    //分析需求:下载是耗时的操作,需要在子线程异步执行
    //准备控件的工作UIImageView/UIScrollView(根视图)
    //
    @implementation ViewController
    /*
     loadView:优先于viewDidLoad
     loadView:当self.view == nil 时调用
     loadView:不需要调用super
     */
    -(void)loadView{
        //创建根视图
        self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
        //把根视图替换成scrollView
        self.view = self.scrollView;
        self.scrollView.backgroundColor = [UIColor redColor];
        
        //创建图片子视图
        UIImageView *imgView = [[UIImageView alloc] init];
        [self.view addSubview:imgView];
        //给属性赋值:一定不能少
        self.imgView = imgView;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        //[self laodImageData];
        [self performSelectorInBackground:@selector(loadImageData) withObject:nil];
    }
    
    //在子线程下载图片,在主线程更新UI,是线程间通信的一种
    //线程间通信:一个线程把他执行的结果,传递到另外的一个线程
    
    //下载图片的主方法
    -(void)loadImageData{
        //URL
        NSURL *url = [NSURL URLWithString:@"http://img05.tooopen.com/images/20150202/sy_80219211654.jpg"];
        //发送网络请求,获取图片二进制数据,是个耗时的操作
        NSData *data = [NSData dataWithContentsOfURL:url];
        //image就是子线程执行的结果,需要传递到主线程
        UIImage *image = [UIImage imageWithData:data];
        
        //下载完成后,通知主线程刷新UI
        //waitUntilDone:是否等到updateUI执行完,再执行后面的代码,一般传入NO
        [self performSelectorOnMainThread:@selector(updataUI:) withObject:image waitUntilDone:NO];
        NSLog(@"后面的代码");
    }
    -(void)updataUI:(UIImage *)image{
        //下载完成后,刷新UI
        self.imgView.image = image;
        [self.imgView sizeToFit];
        self.scrollView.contentSize = image.size;
    }
    

    异步下载

    info.plist中添加代码,允许请求网络
    <key>NSAppTransportSecurity</key>
    <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
    </dict>

    相关文章

      网友评论

          本文标题:多线程1

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