zoukankan      html  css  js  c++  java
  • 多线程与网络线程的几种创建方法

    线程的创建方法

    pthread

    • 创建 pthread_create
    • 只要create一次就会创建一个新的线程
    • 系统会自动在子线程中调用传入的函数

      {
          // 将耗时的操作放在子线程中
          /*
          第一个参数:pthread_t *restrict 线程的代号
          第二个参数:const pthread_attr_t *restrict 线程的属性
          第三个参数:void *(*)(void *) 指向函数的指针,将来线程需要执行的方法
          第四个参数:void *restrict 给第三个参数的指向函数 传递的参数
          */
          pthread_t threadID;
          // 只要create一次就会创建一个新的线程
          pthread_create(&threadID, NULL, &demo, @"xiao");
      }
      void *demo(void * index) {
          for (int i = 0; i < 100; ++i) {
              NSLog(@"%i------%@", i, [NSThread currentThread]);
          }
          return NULL; // 比较特殊,要有返回值
      }

    NSThread

    注意点:死了不能再活过来(即要重新调用)

    • 1.几种创建方式

      • 1)alloc-initWithTarget方法

        • 注意:需要手动调用start方法启动线程
        • 特点:系统内部会retain当前线程
        • 生命周期:只有线程中的方法执行完毕,系统才会将其释放
        • 代码:

              // 1.创建一个新的线程
               NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
              // 2.启动线程
              [thread start];
      • 2)detachNewThreadSelector:方法

        • 注意:不需要手动调用start方法启动线程
        • 缺点:没有返回值,不能对线程进行更多的设置
        • 应用场景:需要快速简便的执行线程
        • 代码:

          // 分离出一个新的子线程
          [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
      • 3)performSelectorInBackground:方法

        • 注意:不需要手动调用start方法启动线程,当前控制器调用(self)
        • 缺点:没有返回值,不能对线程进行更多的设置
        • 应用场景:需要快速简便的执行线程
        • 代码:

          // 用后台线程调用run:函数
          [self performSelectorInBackground:@selector(run:) withObject:nil];
      • 4)相关属性

    • 2.线程状态

      • 新建状态(New):创建出来
      • 就绪状态(Runnable):调用start
      • 运行状态(Running):被CUP调用
      • 阻塞状态(Blocked):调用了sleep方法/等待同步锁

        // 指定时间(阻塞线程,阻塞5秒的时间)
        [NSThread sleepForTimeInterval:5.0f];
        // 指定日期(阻塞线程,阻塞到现在开始的2秒钟之后)
        [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
        // 指定日期(睡眠醒不来)
        [NSThread sleepUntilDate:[NSDate distantFuture]];
        [NSThread exit];
      • 死亡状态(Dead):线程任务执行完毕/异常/强制退出(exit)

    • 3.互斥锁(安全隐患)

      • 应用场景:多线程存在资源抢夺(多个线程同时访问某个文件/变量等)
      • 注意点:
        • 只要枷锁就会消耗性能
        • 如果想真正的锁住代码, 那么多个线程必须使用同一把锁才行
        • 加锁的时候尽量缩小范围, 因为范围越大性能就越低
      • 技巧:如何快速记住加锁的单词

        • [NSUserDefaults standardUserDefaults] synchronize快速记忆的方法

          // self是锁对象:唯一性
          @synchronized:(self) {
              // 加锁的内容
          }
      • 专业术语:线程同步(多条线程在同一条线上执行)

    • 4.原子和非原子

      • atomic:原子属性,为setter方法加锁,线程安全,性能低
      • nonatomic:非原子属性,不会为setter方法加锁,线程不安全,性能高
    • 5.线程间通信

      • 体现:
        • 1.一个线程传递数据给另一个线程
        • 2.在一个线程中执行完特定任务后,让另一个线程执行接下来的任务
    • 附:下载图片

      • 1.获得下载图片的url

        NSURL *url = [NSURL URLWIthString:@"图片下载地址"];

      • 2.下载图片的二进制数据到本地(花费时间最长)

        NSData *imageData = [NSData dataWithContentsOfURL:url];

      • 3.把二进制数据转换成image

        UIImage *image = [UIImage imageWithData:imageData];

      • 4.回到主线程刷新UI(设置图片)很多个方法

        [self performSelector:@selector(showImage:) withObject...];

      • 5.在showImage中设置图片 self.imageView.image = image;

    GCD

    • 1.简介:(Grand Central Dispatch)牛逼的中枢调度器
    • 2.特点:
      • 为多核的并行运算提出的方案
      • 自动利用更多的cup内核
      • 自动管理线程的生命周期
      • 缺少
    • 3.任务和队列:执行什么操作,用来存放任务
    • 4.同步和异步(能不能开线程)---封装任务,添加任务到队列中

      • 同步: 只能在当前线程中执行任务,不具备开启新线程的能力,要求立刻执行
      • 异步: 可以在新的线程中执行任务,具备开启新线程的能力
      • 对应的两个函数

        同步函数:dispatch_sync
        异步函数:dispatch_async
    • 5.队列类型(任务的执行方式)---保持任务,安排|调度任务

      • 并发队列: 允许多个任务并发(同时)执行
      • 串行队列: 一个任务执行完毕后,再执行另一个任务
    • 6.GCD的基本使用

      • 01 异步函数+并发队列:开启多条线程,并发执行任务
      • 02 异步函数+串行队列:开启一条线程,串行执行任务
      • 03 同步函数+并发队列:不开线程,串行执行任务
      • 04 同步函数+串行队列:不开线程,串行执行任务
      • 05 异步函数+主队列:不开线程,在主线程中串行执行任务
      • 06 同步函数+主队列:不开线程,串行执行任务(注意死锁发生)
        • 主队列特点:如果发现主线程正在执行代码,那么就暂停调度队列里面的任务,即是死锁现象,需要设置一个子控制器来执行
      • 07 注意同步函数和异步函数在执行顺序上面的差异

        • 异步函数:不需要当前代码执行完毕,就可以执行后面的代码
        • 同步函数:要等到当前代码执行完毕,才可以继续往下执行(一直等待)

    • 7.GCD的常用通信代码模板

      • 1.创建队列: 保存任务,安排|调度任务

        // 1.创建队列: 保持任务,安排|调度任务
        /*
        第一个参数:const char *label 字符串
        第二个参数:dispatch_queue_attr_t attr 指示出是并发还是串行
        DISPATCH_QUEUE_CONCURRENT 并发
        DISPATCH_QUEUE_SERIAL 串行
        */
        dispatch_queue_t queue = dispatch_queue_create("xiao", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t queue = dispatch_queue_create("xiao", DISPATCH_QUEUE_SERIALS);
        /*
        第一个参数:long identifier 队列的优先级, DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
        第二个参数:unsigned long flags 此参数暂时无用,设置为0
        */
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      • 2.创建同步/异步函数: 封装任务,添加任务到队列中

        // 2.异步函数: 封装任务,添加任务到队列中
        dispatch_async(queue, ^{
        // 执行代码
        });
        // 2.同步函数: 封装任务,添加任务到队列中
        dispatch_sync(queue, ^{
        // 执行代码
        });
      • 3.下载图片小事例

      // 1.创建队列(全局并发队列)
      dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
      
      // 2.下载图片(耗时)-->放在子线程中(异步函数)
      dispatch_async(queue, ^{
          // 创建url
          NSURL *url = [NSURL URLWithString:@"http://www.qqjia.com/z/06/tu8000_5.jpg"];
      
          // 通过url将图片转换成二进制NSData
          NSData *data = [NSData dataWithContentsOfURL:url];
      
          // 将NSData转换成图片
          UIImage *image = [UIImage imageWithData:data];
      
          // 更新UI --> 在主线程中
          // 如果是通过异步函数调用, 那么会先执行完所有的代码, 再更新UI
          // 如果是同步函数调用, 那么会先更新UI, 再执行其它代码
          dispatch_async(dispatch_get_main_queue(), ^{
              NSLog(@"%@", [NSThread currentThread].name);
              self.imageView.image = image;
              NSLog(@"先更新UI");
          });
          NSLog(@"先执行");
      });
    • 8.常用函数

      • 1.GCD的延迟执行

        // DISPATCH_TIME_NOW:从什么时候开始计时(现在)
        // 2.0位置:间隔时间(延迟时间)
        // dispatch_get_main_queue():队列,决定block在哪个线程中调用,当是主队列时就是主线程调用
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            // 代码
        });
      • 2.GCD的栅栏函数(barrier)

        // 栅栏函数:可以控制队列中任务的执行顺序,前面的任务执行完毕后执行后面的任务
        // 注意:这边只能使用并发队列(但是不能用全局并发队列)
        dispatch_barrier_async(queue, ^{
            NSLog(@"---------");
        };
      • 3.一次性代码函数

        // 保证整个程序运行中过程中执行一次,不能放在懒加载中,会为空
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"----once----");
        };
      • 4.快速迭代函数

        // 第一个参数:size_t iterations 遍历的次数
        // 第二个参数:dispatch_queue_t queue 队列,决定block在哪个线程调用,并发队列
        // 第三个参数:^(size_t) (需要在size_t后面加上一个参数名)索引
        dispatch_apply(size_t iterations, dispatch_queue_t queue, ^(size_t) {
        // 代码
        });
      • 5.队列组

        // 创建队列组
        dispatch_group_t group = dispatch_group_create();
        // 创建队列
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        // 使用队列组的异步函数,添加数组,监听队列里面任务的执行情况
        dispatch_group_async(group, queue, ^{
            // 代码
        }
        // 第一种:(拦截通知)当所有任务都执行完毕后,来到该方法
        dispatch_group_notify(group, queue, ^{
            // 代码
        }
        // 第二种:(拦截通知)一直等待,等待所有的任务都执行完毕后继续往下执行 | 阻塞
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        • 小案例:下载两张图片,合成图片

          代码
    • 9.使用create函数创建的并发队列和全局并发队列的区别

      • 1.全局并发队列在整个应用程序中本身是默认存在的,并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只是选择其中的一个直接拿来用。而Crearte函数是实打实的从头开始去创建一个队列。
      • 2.在iOS6.0之前,在GCD中凡是使用了带Crearte和retain的函数在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。当然了,在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就OK。
      • 3.在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效(没有给出具体原因)

    NSOperation

    • 1.概念:
      • NSOperation是对GCD的包装
      • 两个核心概念[队列+操作]
    • 2.NSOperation基本使用:

      • NSOperation本身是抽象类,只能使用它的子类
      • 三个子类:NSBlockOperation/NSInvocationOperation/自定义继承NSOperation的类
      • NSOperation和NSOperationQueue配合使用实现多线程编程
      • 相关代码:

        • 1) NSBlockOperation

          //1.封装操作
          NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
          // 主线程执行
              NSLog(@"download1---%@",[NSThread currentThread]);
          }];
          // 追加任务
          // 追加的任务在子线程中并发执行
          [op1 addExecutionBlock:^{
              NSLog(@"download4---%@",[NSThread currentThread]);
          }];
          //2.开始执行
          [op1 start];
        • 2) NSInvocationOpeartion

          //1.封装操作
          /*
          第一个参数:目标对象 self
          第二个参数:调用方法
          第三个参数:调用方法需要传递的参数
          */
          NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
          //2.启动操作
          [op1 start];
        • 3) 自定义继承NSOperation的类

          // 自定义一个继承NSOperation的类(重写main函数)
          - (void)main
          {
              NSLog(@"1---%@", [NSThread currentThread]);
          }
          // 封装操作
          LJSubOperation *subOperation1 = [[LJSubOperation alloc] init];
          LJSubOperation *subOperation2 = [[LJSubOperation alloc] init];
          // 开始执行
          [subOperation1 start];
          [subOperation2 start];
    • 3.NSOperationQueue基本使用

      • 两种队列
        • 主队列 通过mainQueue获得,凡是放到主队列中的任务都将在主线程执行
        • 非主队列 直接alloc init出来的队列。非主队列同时具备了并发和串行的功能,通过设置最大并发数属性来控制任务是并发执行还是串行执行
      • 相关代码:

        // 1.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        // 2.将操作添加到队列中(会自动调用start方法)
        [queue addOperation:bOpertion1];
        //简便方法:该方法内部会自动将block块里面的任务封装为一个NSBlockOperation对象,然后添加到队列
        [queue addOperationWithBlock:^{
            // 代码
        }];
    • 4.NSOperation的其它用法

      • 4.1 设置最大并发数(控制任务并发和串行)

        // 注意点:该属性需要在任务添加到队列中之前进行设置
        // 该属性控制队列串行还是并发执行
        // 如果该属性设置1,则为串行,大于1则就是并发的
        // 系统默认的值位-1,如果该属性设置为0,则不会执行任何任务
        queue.maxConcurrentOperationCount = 2;
      • 4.2 暂停.恢复以及取消

        // 暂停,只是不执行当前任务下面的操作,但是当前的还是会执行
        self.queue.suspended = YES;
        // 恢复
        self.queue.suspended = NO;
        // 取消队列中的所有操作,不可以恢复
        [self.queue cancelAllOperations];
        // 注意:苹果官方建议,每当执行完一次耗时操作之后,就查看一下当前队列是否为取消状态,如果是,那么就直接退出
        // 好处是可以提高程序的性能
        if (self.isCancelled) {
            return;
        }
    • 5.操作依赖和操作监听

      // op1和op2都是NSOperation操作,能保证操作1依赖于操作2,执行2再执行1
      // 如果互相都依赖对方,那么两个都不依赖
      [op1 addDependency:op2];
    • 6.小案例

      • 6.1 下载图片
      看附件
      • 6.2 图片下载综合案例
      看附件
      // 1.数据展示
      // 2.内存存储(存储图片)NSMutableDictionary *iamges
      // 3.沙盒存储(Libriary/caches)
      NSString *caches = [NSSearchPathForDirectoriesInDimains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
      // 4.问题1:数据混乱(重复下载)-->将操作加到操作缓存中
      // 问题2:复用问题:判断之前设置占位图片self.imageView.image = nil;
      // 问题3:数据容错处理
      // 发生内存警告:1.清空内存缓存 2.关闭所有的队列操作 3.清空所有的下载操作字典
    • 7.附录

      • addEd..内部调用的是start方法,start内部调用的是main
  • 相关阅读:
    Undergound Heaven [only_for_information]
    Essential Booklist of .Net Framework
    Thinkpad T4x 风扇转速档位控制
    Hot scene AGAIN!
    JavaScript使用技巧精萃
    今天项目中遇到的一个问题:判断新闻Id是否存在
    C++编译过程中"没有找到MFC80UD.DLL,因此这个程序未能启动.重新安装应用程序可能会修复此问题"? 的彻底解决
    SQL操作全集
    关于UrlReferrer传值的几点注意
    在ASP.Net2.0中使用UrlRewritingNet实现链接重写(转)
  • 原文地址:https://www.cnblogs.com/LongLJ/p/5084341.html
Copyright © 2011-2022 走看看