美文网首页
二. NSThread基本使用

二. NSThread基本使用

作者: 面糊 | 来源:发表于2016-05-25 21:36 被阅读21次

    一. 线程的创建

    1. 创建线程并且手动开启, 同时在这条线程执行selector的任务

       // 1. 创建线程对象
       NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(task1:) object:@"创建线程方式1"];
       // 2. 为线程添加标识   
       [thread1 setName:@"thread1"];           
       // 3. 设置线程的优先级(优先级高的线程,会优先执行)       
       [thread1 setThreadPriority:0.5];       
       // 4. 开始执行
       [thread1 start];
      
    2. 分离出一条子线程, 同时在这条线程执行selector的任务

       // 2. 分离子线程
       [NSThread detachNewThreadSelector:@selector(task1:) toTarget:self withObject:@"创建线程方式2"];           
      
    3. 开启一条后台线程, 同时在这条线程执行selector的任务(注意, 该方法是由当前控制器调用的)

         // 3. 创建后台线程
         [self performSelectorInBackground:@selector(task1:) withObject:@"创建线程方式3"];
      
    4. 自定义线程

      • 创建一个NSThread的子类
      • 在类中重写-(void)main方法, 将这个线程需要执行的任务, 在这个方法中实现
      • 使用alloc/init创建这个自定义线程, 当使用start执行这个线程的时候, 系统会自动调用main方法来执行其中的任务
    5. 创建线程方法的优缺点

      • 手动创建线程, 可以获取线程对象, 可以对这个线程对象进行详细的设置, 如标识/优先级等
      • 而采用分离子线程或者开启后台线程的方法, 使用方法简单, 可以直接开启一个子线程去执行耗时任务, 但是由于无法获取到这个线程对象, 因此无法对其进行设置

    二. 线程的生命周期

    • 线程的五种状态
      • New: 一个刚刚新建出来的线程, 此时没有任何任务在执行当中
      • Runable: 处于可调度线程池的线程, 只有在线程池中的线程, 才可以被CPU调度
      • Running: 使用了-(void)start方法, 被激活工作, 且正在被CPU调度的线程, 一个线程如果还未完成它所有的任务, 那么他就会一直在Runable和Running两个状态中相互切换
      • Blocked: 调用了[NSThread sleepForTimeInterval]方法或等待同步锁的放行时的状态, 此时线程暂时被移除可调度线程池, 但是并没有被销毁掉, 当sleep的时间到了或同步锁放行, 就会恢复Runable状态
      • Dead: 当一条线程的任务执行完毕/异常或强制退出exit方法时, 这条线程就会被销毁, 销毁后的线程与对象一样, 不能再次使用

    三. 线程安全

    1. 多线程应用时的安全隐患
      • 当多个线程, 出现访问同一块资源的时候, 这样会导致在存取的过程中, 被取资源中保存的值由于可能同时在修改, 导致值发生错误

        // 例子: 三个售票员同时售票
          - (void)sale {
             while (1) {
                 @synchronized(self) {  // 如果这里不增加线程锁, 就会导致存取错误
                     // 1. 检查余票
                     int count = self.count;
                     if (count > 0) {
                         // 演示耗时操作
                         for (int i = 0; i < 100000; i++) {
                             // 如果线程的任务中有耗时操作,就有可能引起共同访问一块资源导致数据错误
                             // 因此这时需要加入线程锁
                         }
                         // 访问属性
                         self.count = count - 1;
                         NSLog(@"%@卖出去了一张票,还剩%d张", [NSThread currentThread], self.count);
                         
                     } else {
                         NSLog(@"票已经卖完");
                         [NSThread exit];
                     }
                 }
             }
          }
        
      • 解决方法: 在线程要访问资源之前, 增加一个互斥锁
        @synchronized(锁对象){ 需要锁定的代码 }

      • 互斥锁:

        • 通常锁对象, 是全局唯一的一个对象, 一般使用NSObject作为对象的类型
        • 注意点
          1. 一份代码, 只能使用同一把互斥锁
          2. 锁对象本身有两种状态: 打开/关闭
          3. 当互斥锁关闭的时候, 队列中的线程就会进入Blocked状态, 直到互斥锁打开, 该线程才会进入运行状态
          4. 锁对象可以使用当前的控制器, 也就是self
          5. 加锁的位置需要注意, 位置不同, 执行的代码也会不同
          6. 加锁的前提条件: 只有出现多个线程同时访问同一块资源的时候, 才需要使用互斥锁进行保护
          7. 互斥锁的使用, 会增加额外的资源消耗, 所以能不使用就不要使用, 尽量避免出现多线程抢夺资源
          8. 互斥锁会造成线程同步: 每个线程会在锁的外面排队执行任务
          9. 但是队列中的线程是异步的: 到底是哪条线程访问锁内的资源, 顺序是不确定的

    四. 原子和非原子性

    1. atomic:

      • 属性为原子性, 该关键字会为setter方法增加一个互斥锁, 因此它是线程安全的
      • 但是他会消耗大量的内存资源, 并且在我们的开发过程中, 很少发生多线程访问同一个属性的情况, 因此基本不会使用这个关键字
    2. nonatomic:

      • 属性为非原子性, 此关键字不会为setter方法增加互斥锁, 因此是非线程安全的, 适合内存小的移动设备, 即iOS开发中属性主要使用的关键字,
      • 因此, 在日常开发中, 尽量要避免多线程抢夺资源的情况, 加锁/资源抢夺的业务逻辑通常会交由服务器端来处理, 即每次只能接收/发送一份网络请求

    五. 线程间的通信

    1. 需求:
      • 开启条子线程, 并且下载一张图片

      • 将获得到的图片, 在主线程中设置给imageView

      • 注意点: 遵循耗时操作交给子线程, UI操作回到主线程

          // 0. 创建子线程执行下载任务线程
          NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download) object:nil];
          [thread start];
          
          - (void)download {
          // 计算时间(绝对时间)
          CFTimeInterval start = CFAbsoluteTimeGetCurrent();    
          // 1. 创建url路径
          NSURL *url = [NSURL URLWithString:@"http://dimg07.c-ctrip.com/images/tg/946/212/497/81b56770ed4544a6a8a1125fb381753d_C_640_640.jpg"];
          // 2. 将图片转换为二进制数据
          NSData *data = [NSData dataWithContentsOfURL:url];    
          // 3. 转换格式(二进制 -> UIImage)
          UIImage *image = [UIImage imageWithData:data];    
          // 4. 回主线程设置图片
          //    [self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:NO]; // 该方法可以直接使用主线程去执行任务
          [self performSelector:@selector(showImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];    
          // 获取绝对时间
          CFTimeInterval end = CFAbsoluteTimeGetCurrent();    
          NSLog(@"%f", end - start);
          }

    相关文章

      网友评论

          本文标题:二. NSThread基本使用

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