一、pthread(很少使用)

简介:

  • 一套通用的多线程API
  • 使用与Unix、Linux、Window等系统
  • 跨平台、可移植
  • 使用难度大

    特点:

  • C语言
  • 程序员管理线程生命周期

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

pthread_t thread;

NSLog(@"%@", [NSThread currentThread]); // 主线程中执行

pthread_create(&thread, NULL, run, NULL); // 在子线程中执行演示的操作

}

/** 延时操作 */
void * run(void *param) {

// 耗时操作
for (NSInteger i = 0; i < 10000; i++) {
NSLog(@"%zd", i);
}

NSLog(@"%@", [NSThread currentThread]); // 子线程中执行
return NULL;
}

二、NSTread(偶尔使用)

简介:

  • 使用更加面向对象
  • 使用简单,可直接操作线程对象

特点:

  • OC语言
  • 程序员管理线程生命周期

线程状态:

线程状态.png

常用方法:

  • 创建子线程-方式一

    1
    2
    3
    4
    5
    6
    // 创建一个子线程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
    // 设置创建的线程的名字
    [thread setName:@"sonThread"];
    // 开启这个线程
    [thread start];
  • 创建子线程-方式二

    1
    2
    // 方式二: 创建后无需手动开启,系统会自动开启
    [NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];
  • 创建子线程-方式三

    1
    2
    // 方式三:隐式创建并自定开启
    [self performSelectorInBackground:@selector(test) withObject:nil];
  • 获得当前线程

    1
    NSThread *current = [NSThread currentThread];
  • 线程的名字

    1
    2
    3
    4
    // 设置线程的名字
    - (void)setName:(NSString *)name;
    // 获取线程的名字
    - (NSString *)name;
  • 启动线程

    1
    2
    - (void)start;
    // 进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
  • 阻塞线程

    1
    2
    3
    + (void)sleepUntilDate:(NSDate *)date;
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    // 进入阻塞状态
  • 强制杀死线程

    1
    2
    + (void)exit;
    // 进入死亡状态
    • 注意:一旦线程死亡(停止),就不能再次开启

线程安全

  • 线程安全隐患
    线程安全隐患.png

  • 解决方式:互斥锁
    线程安全解决方式.png

  • 互斥锁的使用前提:多条线程抢夺同一块资源

  • 互斥锁的优缺点:

    • 优点:能有效防止因多线程抢夺资源造成的数据安全问题
    • 缺点:需要消耗大量的CPU资源
  • 相关专业术语:线程同步

线程通信

  • 在1个线程中执行完特定任务后,转到另1个线程继续执行任务
  • 线程通信常用方法
1
2
3
4
// 在主线程执行SEL
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
// 在指定的thread执行SEL
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thread withObject:(id)arg waitUntilDone:(BOOL)wait;

三、GCD(经常使用)

  • 简介:

    • 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”
    • 旨在替代NSTread等线程技术
    • 充分利用设备的多核
  • 特点:

    • C语言
    • 系统自动管理线程生命周期(创建线程、调度任务、销毁线程)
  • GCD两个重要概念:任务队列

    • 任务:执行什么操作(任务)
    • 队列:用来存放任务
  • GCD的使用步骤:

    • 1.定制任务
    • 2.将任务添加到队列中
    • 注意:任务的取出(执行),按照队列的FIFO原则(先进先出,后进后出)
  • GCD有四个用来执行任务的常用函数

    • 同步执行 :只能在当前线程中执行任务,不具备开启新线程的能力

      1
      dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    • 异步执行:可以(但不一定)在新的线程中执行任务,具备开启新线程的能力

      1
      2
      3
      dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
      dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
      dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
  • 队列的类型:(Serial Dispatch Queue和Concurrent Dispatch Queue)

    • 1.串行队列:让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
    • 2.并发队列:可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
    • 注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效
  • 同步、异步、并发、串行

    • 同步和异步主要影响:能不能开启新的线程
      • 同步:只是在当前线程中执行任务,不具备开启新线程的能力
      • 异步:可以(但不是一定)新的线程中执行任务,具备开启新线程的能力
    • 并发和串行主要影响:任务的执行方式
      • 并发:允许多个任务并发(同时)执行
      • 串行:一个任务执行完毕后,再执行下一个任务
  • 队列的创建

    • 并发队列的创建:

      1
      2
      // 创建并发队列
      dispatch_queue_t queue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
      • GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建,我们只要获取就可以使用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      dispatch_queue_t dispatch_get_global_queue(
      dispatch_queue_priority_t priority, // 队列的优先级
      unsigned long flags); // 此参数暂时无用,用0即可(官方文档)

      // 获得全局并发队列
      dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

      // 全局并发队列的优先级
      define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
      define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
      define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
      define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
    • 串行队列的创建:

      • 1.创建一个串行队列:
      1
      dispatch_queue_t queue = dispatch_queue_create("com.YotrolZ", DISPATCH_QUEUE_SERIAL);
        - 注意:队列类型传入`NULL`也可以;    
      
      • 2.获取系统自带的一种特殊的串行队列-主队列
      1
      dispatch_queue_t queue = dispatch_get_main_queue()
      • 主队列的特点:放在主队列中的任务,都是在主线程中执行
      • 注意点:
        • 在串行队列中使用同步函数添加任务时会出现卡主当前串行队列的现象!!
        • 解释:在串行队列中,任务是一个一个按顺序执行了,当使用同步函数往队列中添加任务时理论上要立即执行改任务,但由于串行队列还没有执行完毕,理论上又轮不到刚添加的任务执行,会出现你等我,我等你的现象
  • GCD的其他使用:

    • 1.延时执行

      1
      2
      3
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      // 这里协商2秒后要执行的代码
      });
    • 2.一次性代码(让某段代码在整个程序运行过程中只运行一次)

      • 一次性代码函数是线程安全
      1
      2
      3
      4
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
      // 这里写上只执行1次的代码
      });
    • 3.快速迭代

      • 注意点:index顺序不确定
        1
        2
        3
           dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
        // 执行10次代码
        });
    • 4.队列组

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
         dispatch_group_t group =  dispatch_group_create();
      dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // 执行1个耗时的异步操作
      });
      dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      // 执行1个耗时的异步操作
      });
      dispatch_group_notify(group, dispatch_get_main_queue(), ^{
      // 等前面的异步操作都执行完毕后,回到主线程执行此操作
      });

四、NSOperation+NSOperationQueue(经常使用)

  • 简介:
    • NSOperation基于GCD(底层是GCD),性能肯定也就没有GCD高了
    • 比GCD多了一些更简单的功能
    • 使用更面向对象
  • 特点:
    • OC语言
    • 系统自动管理线程生命周期

NSOperation的使用

  • NSOperation是一个抽象类,并不具备封装任务的功能,我们应该使用其子类(3种)

  • NSBlockOperation

    • NSBlockOperation使用方法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      NSBlockOperation *blockOP = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"blockOperation1--%@", [NSThread currentThread]); // 在主线程执行
      }];
      [blockOP addExecutionBlock:^{
      NSLog(@"blockOperation2--%@", [NSThread currentThread]); // 在子线程执行(自动开启线程)
      }];
      [blockOP addExecutionBlock:^{
      NSLog(@"blockOperation3--%@", [NSThread currentThread]); // 又会开启一个新的子线程
      }];

      [blockOP start];
    • NSBlockOperation使用细节:

      • 1.单独使用blockOperation且任务只有一个时,不会开启新的线程,在当前线程中执行;
      • 2.为blockOperation再次添加新的任务(任务数大于1)会开启新的线程,在子线程中执行;
  • NSBlockOperation

    • NSInvocationOperation使用方法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      -(void)invocationOperation {
      // 创建任务
      NSInvocationOperation *invocationOP = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
      // 开始任务
      [invocationOP start];
      }
      -(void)run {
      NSLog(@"invocationOperation--%@", [NSThread currentThread]);
      }
    • NSInvocationOperation使用细节:

    • 单独使用operation不会开启新的线程,在当前线程中执行

  • 自定义Operation

    • 步骤1.定义一个子类继承NSOperation;

    • 步骤2.实现其- (void)main;方法,在main方法中添加要执行的任务操作;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      - (void)main { // 添加要执行的任务操作
      if (self.isCancelled) return;
      for (NSUInteger i = 0; i < 1000; i++) {
      NSLog(@"%zd--%@", i, [NSThread currentThread]);
      }

      if (self.isCancelled) return;
      for (NSUInteger i = 0; i < 1000; i++) {
      NSLog(@"%zd--%@", i, [NSThread currentThread]);
      }

      if (self.isCancelled) return;
      for (NSUInteger i = 0; i < 1000; i++) {
      NSLog(@"%zd--%@", i, [NSThread currentThread]);
      }
      }
    • 自定义Operation使用细节:

      • 重写Operation的mian方法时,官方建议我们及时的判断isCancelled,以能够及时的取消一些耗时的操作;
  • NSOperation的其他使用

    • 操作的依赖监听

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
      // 创建队列
      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
      // 创建5个任务操作
      NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"op1--%@", [NSThread currentThread]);
      }];
      NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"op2--%@", [NSThread currentThread]);
      }];
      NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"op3--%@", [NSThread currentThread]);
      }];
      NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"op4--%@", [NSThread currentThread]);
      }];
      NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"op5--%@", [NSThread currentThread]);
      }];

      // 操作的监听
      op4.completionBlock = ^{
      // 也是在子线程中执行,但是不一定和器任务操作在一个线程
      NSLog(@"op4完成了--%@", [NSThread currentThread]);
      };

      // 设置依赖(可以设置多个,但是不能相互依赖)
      // 这样就保证了op3在op1和op4完成后才执行
      [op3 addDependency:op1];
      [op3 addDependency:op4];

      // 将任务操作添加到队列中
      [queue addOperation:op1];
      [queue addOperation:op2];
      [queue addOperation:op3];
      [queue addOperation:op4];
      [queue addOperation:op5];
      }

NSOperationQueue的使用

  • NSOperationQueue的作用

    • NSOperation可以调用start方法来执行任务,但默认是同步执行的,不一定会开启新的线程
    • 将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
  • 将任务(NSOperation)添加到操作队列(NSOperationQueue0中

    • 方式一:需先创建NSOperation,再添加到NSOperationQUeue中

      1
      -(void)addOperation:(NSOperation *)op;
    • 方式二:不需先创建NSOperation,直接在添加到NSOperationQueue的时候在block中写上任务代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
            -(void)addOperationWithBlock:(void (^)(void))block;

      NSOperationQueue *queue = [[NSOperationQueue alloc] init];
      // 任务1
      [queue addOperationWithBlock:^{
      NSLog(@"11--%@", [NSThread currentThread]);
      for (NSUInteger i = 0; i < 10; i++) {
      NSLog(@"%zd", i);
      [NSThread sleepForTimeInterval:1];
      }
      }];
      // 任务2
      [queue addOperationWithBlock:^{
      NSLog(@"22--%@", [NSThread currentThread]);
      for (NSUInteger i = 0; i < 10; i++) {
      NSLog(@"%zd", i);
      }
      }];
      // 任务3
      [queue addOperationWithBlock:^{
      NSLog(@"33--%@", [NSThread currentThread]);
      for (NSUInteger i = 0; i < 10; i++) {
      NSLog(@"%zd", i);
      }
      }];
    • 备注:添加到NSOperationQueue中的任务系统会自动异步执行,不用start方法

  • NSOperationQueue的常见属性:

    • 1.最大并发数:同时执行的任务数

      1
      @property NSInteger maxConcurrentOperationCount;
      • 备注:设置NSOperationQueuemaxConcurrentOperationCount1时,也就是同时执行一个任务,(同步执行)
    • 2.暂停(挂起状态)

      1
      @property (getter=isSuspended) BOOL suspended;
      • 备注:设置NSOperationQueuesuspendedYES时,会暂停(挂起)队列中的任务,但是如果某个任务已经在执行了,并不会立即暂停这个任务,而是等待这个任务执行完毕后,暂停后面的其他任务;
      • 使用场合:暂停(挂起)这个功能,可以用在(假如正在下载一组图片,用户拖拽了界面控件等,为了保证流畅的用户体验,可以先挂起,等用户拖拽结束,再恢复)的情况下使用
  • NSOperationQueue取消所有任务:

    1
    -(void)cancelAllOperations;
    • 备注:NSOperationQueue对象调用cancelAllOperations方法时,会取消队列中的所有任务,但是如果某个任务已经在执行了,并不会立即取消所有任务,而是等待这个任务执行完毕后,取消所有任务;