多线程与网络线程的几种创建方法

线程的创建方法

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
原文地址:https://www.cnblogs.com/LongLJ/p/5084341.html