iOS多线程

iOS多线程

多线程基础

在学习多线程之前, 按照惯例, 先普及一些概念, 为接下来的学习做铺垫.

进程

进程是指在系统中正在运行的一个应用程序, 在一个系统中, 进程之间是相互独立的. 每个进程运行在其专用且收保护的内存空间

线程

进程想要执行任务, 必须在线程下执行, 每1进程至少要有一条线程, 我们称之为主线程.

线程与进程的关系

一个进程中至少包含一个线程, 同一个进程内的线程共享进程的资源.

主线程

一个iOS程序运行时, 默认会开启1条线程, 称为主线程UI线程.
主线程的作用: 刷新UI 显示UI界面, 处理UI事件, 比如点击, 滑动

使用主线程注意:
不要把耗时操作放到主线程中, 会影响UI的流畅度

多线程

一个进程中可以开启多条线程, 多条线程可以同时执行不同的任务, 多线程技术可以提高程序的执行效率.

多线程原理

对于单核CPU, 同一时间, CPU只能处理1线程, 多线程并发执行, 其实是CPU快速的在多条线程之间来回切换, 如果CPU调度线程到的速度足够快, 就造成了多线程并发的假象.

多线程的优点

  • 能适当提高程序的执行效率
  • 能适当提高资源利用率(CPU, 内存利用率)

多线程的缺点

  • 创建线程是需要成本的, iOS下主要包括, 在栈空间的子线程512KB, 主线程1MB, 创建线程大约需要90毫秒的创建时间.
  • 线程越多, CPU在调度线程上的开销就越大
  • 线程越多, 程序设计就越复杂, 因为要考虑到线程之间的通信, 多线程的数据共享

多线程实现方案

在iOS开发中, 有多重多线程方案可以供我们选择, 一般用其中三种

NSThread

  • 使用面向对象
  • 简单易用, 可以直接操作线程对象
  • 使用Objective-C, 由程序员管理线程生命周期

GCD

  • 旨在替代NSThread等多线程技术
  • 充分利用设备的多核
  • 使用C语言, 自动管理线程生命周期

NSOperation

  • 基于GCD, 更加面向对象
  • 使用Objective-C, 自动管理线程的生命周期

NSThread

NSThread是基于线程使用, 较轻量级多线程编程方法(相对于GCDNSOperation), 一个NSThread对象代表一个线程, 需要手动管理线程的生命周期, 处理线程同步等问题.

NSThread的创建

动态创建, 动态方法返回一个新的thread对象, 需要调用start方法来启动线程

NSTHread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadRun) object:nil];

静态创建, 静态方法没有返回值, 如果需要获取新创建的thread, 需要在Selector中调用获取当前线程的方法

[NSThread detachNewThreadSelector:@selector(threadRun) toTarget:self withObject:nil];

设置线程的名字

[newThread setName:@"线程名字"];
newThread.name = @"线程名字";

线程开启

[newThread start];

线程暂停

NSThread的暂停会有阻塞当前线程的效果

// 暂停1秒
[NSThread sleepForTimeInterval:1.0];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];

线程取消

取消线程并不会马上停止并退出线程, 仅仅只作(线程是否需要退出)状态记录

[newThread cancel];

线程停止

停止方法会立即终止除主线程以外的所有线程(无论是否在执行任务)并退出, 需要在掌控所有线程状态的情况下调用此方法, 否则可能会导致内存问题

[NSThread exit];

获取当前线程

[NSThread currentThread];

获取主线程

[NSThread mainThread];

线程优先级设置

iOS8以前用

// 设置线程优先级, 默认0.5 取值范围0.0-1.0
[NSThread setThreadPriority:1.0];

iOS8以后推荐使用 枚举值

[newThread setQualityOfService:NSQualityOfServiceUserInitiated];
NSQualityOfServiceUserInteractive  // 最高优先级, 用于用户交互事件
NSQualityOfServiceUserInitiated    // 次高优先级, 用于用户需要马上执行的事件
NSQualityOfServiceDefault          // 默认优先级, 主线程和没有设置优先级的线程都默认为这个优先级
NSQualityOfServiceUtility          // 普通优先级, 用于普通任务
NSQualityOfServiceBackground       // 最低优先级, 用于不重要的任务
  • 获取指定线程的优先级
[newThread threadPriority];

线程间的通信

常见的有三种:

指定当前线程执行操作

[self performSelector:@selector(threadRun)];
[self performSelector:@selector(threadRun) withObject:nil];
[self performSelector:@selector(threadRun) withObject:nil afterDelay:2.0];

(在其他线程中)指定主线程执行操作

[self performSelectorOnMainThread:@selector(threadRun) withObject:nil waitUntilDone:YES];

(在主线程中)指定其他线程执行操作

// 这里为指定某个线程
[self performSelector:@selector(threadRun) onThread:newThread withObject:nil waitUntilDone:YES];
// 这里指定为后台线程
[self performSelectorInBackground:@selector(threadRun) withObject:nil];

线程同步

线程和其他线程可能会共享一些资源, 当多个线程同时读写同一份共享资源的时候, 可能会引起冲突. 线程同步是指在一定的时间内只允许某一个线程访问某个资源.
iOS实现线程加锁有NSLock@synchronized两种方式

线程的持续运行和退出

怎样能让线程一直运行呢?
答案就是给线程加上RunLoop

线程状态

线程是否完成

BOOL ret = [newThread isFinished];

线程是否被取消

BOOL ret = [newThread isCancelled];

线程是否正在执行

BOOL ret = [newThread isExecuting];

GCD

GCD全称是Grand Central Dispatch

优势

  • GCD是苹果公司为多核的并行运算提出的解决方案
  • GCD会自动利用更多的CPU内核(比如双核, 四核)
  • GCD会自动管理线程的生命周期(创建线程, 调度任务, 销毁线程)

核心概念 - 任务

任务: 执行什么操作, 任务有两种执行方式: 同步函数异步函数

  • 同步函数(dispatch_sync)
    只能在当前线程中执行任务, 不具备开启新线程的能力,任务会立刻马上执行, 会阻塞当前线程并等待Block中的任务执行完毕, 然后当前线程才会继续往下运行.
  • 异步函数(dispatch_async)
    可以在新的线程中执行任务, 具备开启新线程的能力, 但不一定会开启新线程, 当前线程会直接往下执行, 不会阻塞当前线程.

核心概念 - 队列

队列: 用来存放任务, 分为串行队列并行队列

  • 串行队列(Serial Dispatch Queue)
    让任务一个接着一个的执行, 一个任务执行完毕后再执行下一个任务

  • 并行队列(Concurrent Dispatch Queue)
    可以放多个任务并发, 同时执行(自动开启多个线程同时执行任务)
    并发功能只有在异步函数下才有效.

GCD使用步骤

  • 定制任务
    确定想做的事
  • 将任务添加到队列中
    GCD会自动将队列中的任务取出, 放到对应的线程中去执行, 任务的取出遵循FIFO原则: 先进先出, 后进后出

GCD的创建

队列的创建

     /*
     @param label#> C语言字符串, 用来标识 description#>
     @param attr#> 队列的类型 description#>
     并发队列: DISPATCH_QUEUE_CONCURRENT
     串行队列: DISPATCH_QUEUE_SERIAL 或者 NULL
     @return dispatch_queue_t
     */
dispatch_queue_t queue = dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t  _Nullable attr);

创建串行队列

方法一:

dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_SERIAL);

方法二:

dispatch_queue_t queue = dispatch_queue_create("com.pinyin", NULL);

创建并发队列

dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);

全局并发队列

GCD默认提供了全局并发队列, 供整个应用使用, 可以无需手动创建

@param identifier#> 优先级 也可以直接填后面的数字 description#>
DISPATCH_QUEUE_PRIORITY_HIGH = 2 高
DISPATCH_QUEUE_PRIORITY_DEFAULT = 0 默认
DISPATCH_QUEUE_PRIORITY_LOW = (-2) 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台
@param flags#> 预留参数 0 description#>
@return dispatch_queue_t->全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

获得主队列

dispatch_queue_t queue = dispatch_get_main_queue();

任务的执行

队列在queue中, 任务在block块中.

  • 开启同步函数, 同步函数: 要求立刻马上开始执行
@param queue: 队列
@param block: 执行的任务
dispatch_sync(queue, ^ {
    // 执行任务
}
  • 开启异步函数, 异步函数: 等主线程执行完毕之后, 回过头开线程执行任务
@param queue: 队列
@param block: 执行的任务
dispatch_async(queue, ^{
    // 执行任务
}

任务和队列的组合

任务: 同步函数异步函数
队列: 串行队列并行队列

  • 异步函数+并发队列: 会开启新的线程, 并发执行
    dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"1------%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"2------%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"3------%@", [NSThread currentThread]);
    });

控制台打印:

GCD_OC[18856:17195716] 1------{number = 3, name = (null)}
GCD_OC[18856:17195718] 3------{number = 5, name = (null)}
GCD_OC[18856:17195717] 2------{number = 4, name = (null)}
  • 异步函数+串行队列: 会开启一条线程, 任务串行执行
    dispatch_queue_t queue = dispatch_queue_create("com.pinyin", NULL);
    dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });

控制台打印

GCD_OC[19358:17243144] 1-----{number = 3, name = (null)}
GCD_OC[19358:17243144] 2-----{number = 3, name = (null)}
GCD_OC[19358:17243144] 3-----{number = 3, name = (null)}
  • 同步函数+并发队列: 不会开线程, 任务串行执行
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        NSLog(@"1------%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2------%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3------%@", [NSThread currentThread]);
    });

控制台打印

GCD_OC[19682:17270377] 1------{number = 1, name = main}
GCD_OC[19682:17270377] 2------{number = 1, name = main}
GCD_OC[19682:17270377] 3------{number = 1, name = main}
  • 同步函数+串行队列: 不会开线程, 任务串行执行
    dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });

控制台打印

GCD_OC[19875:17290416] 1-----{number = 1, name = main}
GCD_OC[19875:17290416] 2-----{number = 1, name = main}
GCD_OC[19875:17290416] 3-----{number = 1, name = main}
  • 异步函数+主队列: 不会开线程, 任务串行执行
 dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"------%@", [NSThread currentThread]);
 });

控制台打印

GCD_OC[20048:17307951] ------{number = 1, name = main}
  • 同步函数+主队列 : 死锁
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"%@", [NSThread currentThread]);
    });

死锁原因
首先这是一个同步函数, 同步函数要求立刻马上执行, 我们把这个同步函数的任务加入到主队列, 同步函数代码块中的任务等待主队列任务执行完毕, 然后执行代码块的任务, 主队列在等代码块里的任务执行, 两个任务互相等待发生死锁, 将这个方法放入子线程中, 则不会发生死锁, 任务串行执行

总结

iOS多线程_第1张图片
15353511052679.png

同步函数和异步函数的执行顺序

同步函数: 立刻马上执行, 会阻塞当前线程

    dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"------start-------");
    dispatch_sync(queue, ^{
        NSLog(@"1------%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"2------%@", [NSThread currentThread]);
    });
    dispatch_sync(queue, ^{
        NSLog(@"3------%@", [NSThread currentThread]);
    });
    NSLog(@"------end-------");

控制台打印

GCD_OC[20695:17385076] ------start-------
GCD_OC[20695:17385076] 1------{number = 1, name = main}
GCD_OC[20695:17385076] 2------{number = 1, name = main}
GCD_OC[20695:17385076] 3------{number = 1, name = main}
GCD_OC[20695:17385076] ------end-------

异步函数: 当前线程会直接往下执行, 不会阻塞当前线程

    dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"------start-------");
    dispatch_async(queue, ^{
        NSLog(@"1------%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2------%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"3------%@", [NSThread currentThread]);
    });
    NSLog(@"------end-------");

控制台打印

GCD_OC[20804:17399341] ------start-------
GCD_OC[20804:17399341] ------end-------
GCD_OC[20804:17399604] 3------{number = 5, name = (null)}
GCD_OC[20804:17399605] 2------{number = 4, name = (null)}
GCD_OC[20804:17399607] 1------{number = 3, name = (null)}

GCD线程之间的通信

GCD线程间的通信很简单, 使用同步或异步函数, 传入主队列即可

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // 下载图片
    dispatch_async(dispatch_get_main_queue(), ^ {
         // 更新UI
    });
});

GCD常用函数

栅栏函数

  • 作用: 控制任务的执行顺序
dispatch_barrier_async(queue, ^{

}
    dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"我是栅栏函数");
    });
    dispatch_async(queue, ^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"4-----%@", [NSThread currentThread]);
    });

控制台打印

GCD_OC[21103:17426839] 2-----{number = 4, name = (null)}
GCD_OC[21103:17426841] 1-----{number = 3, name = (null)}
GCD_OC[21103:17426841] 我是栅栏函数
GCD_OC[21103:17426841] 3-----{number = 3, name = (null)}
GCD_OC[21103:17426839] 4-----{number = 4, name = (null)}

延迟执行

    /**
     延迟执行

     @param DISPATCH_TIME_NOW 延迟时间
     @param int64_t 要执行的代码
     @return 如果想要延迟的代码在子线程中执行, 可以将dispatch_get_main_queue()
     改为其他队列
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"------%@", [NSThread currentThread]);
    });

一次性代码

一般用于单例

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行一次的代码
    });

快速迭代

开启多个线程并发完成迭代操作

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    /**
     快速迭代

     @param 5 次数
     @param queue 在哪个队列中执行
     @param block 执行的Block
     */
    dispatch_apply(5, queue, ^(size_t i) {
        NSLog(@"dispatch_apply == == ==........... %lu", i);
    });

控制台打印

GCD_OC[21684:17472387] dispatch_apply == == ==........... 0
GCD_OC[21684:17473701] dispatch_apply == == ==........... 2
GCD_OC[21684:17473703] dispatch_apply == == ==........... 3
GCD_OC[21684:17473702] dispatch_apply == == ==........... 1
GCD_OC[21684:17472387] dispatch_apply == == ==........... 4

队列组

dispatch_group 创建一个任务组, 然后异步执行加入group的每个任务.
一般用于: 异步多请求

    dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        // 执行请求1
        NSLog(@"1------%@", [NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        // 执行请求2
        NSLog(@"2------%@", [NSThread currentThread]);
    });
    dispatch_group_notify(group, queue, ^{
        // 执行完毕, 可以在这里进行更新UI操作
        NSLog(@"finish----%@", [NSThread currentThread]);
    });

控制台打印

GCD_OC[22139:17507032] 1------{number = 3, name = (null)}
GCD_OC[22139:17506993] 2------{number = 4, name = (null)}
GCD_OC[22139:17506993] finish----{number = 4, name = (null)}

dispatch_group_async里的block执行的是异步任务, 如果还是使用上面的方法会发现, 在异步任务还没跑完就已经进入到了dispatch_group_notify方法了, 这时用dispatch_group_enterdispatch_group_leave就可以解决这个问题.
解决方法:

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.pinyin", DISPATCH_QUEUE_CONCURRENT);
    // 进入组
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        // 网络请求1 异步代码块
        {
            // 离开组
            dispatch_group_leave(group);
        }
        NSLog(@"1-----%@", [NSThread currentThread]);
    });
    // 进入组
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        // 网络请求1 异步代码块
        {
            // 离开组
            dispatch_group_leave(group);
        }
        NSLog(@"2-----%@", [NSThread currentThread]);
    });
    dispatch_group_notify(group, queue, ^{
        // 获取到网络请求的数据, 更新UI
        NSLog(@"finish----%@", [NSThread currentThread]);
    });

控制台打印

GCD_OC[23625:17613380] 1-----{number = 3, name = (null)}
GCD_OC[23625:17613378] 2-----{number = 4, name = (null)}
GCD_OC[23625:17613378] finish----{number = 4, name = (null)}

NSOperation

NSOperation是苹果公司对GCD的封装, 完全面向对象, 并且比GCD多了一些简单使用的功能, 所以使用起来更加方便容易理解
优点:

  • 可添加完成的代码块, 在操作完成后执行
  • 添加操作之间的依赖关系, 方便的控制执行顺序
  • 设定操作执行的优先级
  • 可以很方便的取消一个操作的执行
  • 使用KVO观察对操作执行状态的更改:isExecuting, isFinished, isCancelled.

核心概念 - 操作(Operation)

相当于GCD的任务
执行操作的意思, 在GCD中是放在block中的, 在NSOperation中, 我们使用NSOperation的子类NSInvocationOperation, NSBlockOperation 或者自定义子类来封装操作.

核心概念 - 操作队列(Operation Queues)

NSOperation的操作队列和GCD的队列差不多, 都是用来存放操作(任务)的.

  • 不同于GCD的先进先出原则, NSOperationQueue对于添加到队列中的操作, 首先进入准备就绪的状态, 然后进入就绪状态的操作的开始执行顺序由操作之间相对的优先级决定
  • 操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发, 串行
  • NSOperationQueue为我们提供了两种不同类型的队列: 主队列自定义队列
    • 主队列: 运行在主线程之上
    • 自定义队列: 在后台执行

NSOperation, NSOperationQueue使用步骤

NSOperation要配合NSOperationQueue来实现多线程, 因为默认情况下, NSOperation单独使用时系统同步执行操作, 配合NSOperationQueue我们能更好的实现异步执行.

NSOperation实现多线程的使用步骤分为三步:

  • 创建操作: 先将需要执行的操作封装到一个NSOperation对象中
  • 创建队列: 创建NSOperationQueue对象
  • 将操作加入队列中: 将NSOperation对象添加到NSOperationQueue对象中

系统就会自动将NSOperationQueue中的NSOperation取出来, 在新线程中执行操作

创建操作(NSOperation)

NSOperation是一个抽象类, 不能用来封装操作, 我们只能使用它的子类来封装操作, 有三种方式来封装操作.

  • 使用子类NSInvocationOperation
  • 使用子类NSBlockOperation
  • 自定义继承自NSOperation的子类, 通过实现内部相应的方法来封装操作
NSInvocationOperation
- (void)viewDidLoad {
    [super viewDidLoad];
    // 创建NSInvocationOperation 对象
    NSInvocationOperation *opertaion = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"校长"];
    // 调用start方法 执行操作
    [opertaion start];
}

- (void)task:(NSString *)name {
    // 打印当前线程
    NSLog(@"%@", [NSThread currentThread]);
    NSLog(@"%@", name);
}

控制台打印

[53559:18598091] {number = 1, name = main}
[53559:18598091] 校长

可以看到在没有使用NSOperationQueue的情况下, 单独使用NSOperation子类, NSInvocationOperation执行一个操作的情况下, 并没有开启新的线程.

NSBlockOperation
    // 创建NSBlockOperation对象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@", [NSThread currentThread]);
    }];
    // 执行操作
    [operation start];

控制台打印

{number = 1, name = main}

可以看到在没有使用NSOperationQueue, 在单独使用NSBlockOperation执行一个操作的情况下, 操作是当前线程执行的, 并没有开启新线程.

但是, NSBlockOperation还提供了一个方法addExecutionBlock, 通过addExecutionBlock就可以为NSBlockOperation添加额外的操作, 这些操作可以在不同线程并发执行, 只有当所有相关的操作已经完成执行时, 才视为完成.

    // 创建NSBlockOperation对象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"1--------%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"2--------%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"3--------%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"5--------%@", [NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@"6--------%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"7--------%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"8--------%@", [NSThread currentThread]);
    }];
    // 执行操作
    [operation start];

控制台打印

[54248:18665109] 3--------{number = 4, name = (null)}
[54248:18664485] 5--------{number = 5, name = (null)}
[54248:18664486] 2--------{number = 3, name = (null)}
[54248:18661846] 1--------{number = 1, name = main}
[54248:18664485] 7--------{number = 5, name = (null)}
[54248:18664486] 8--------{number = 3, name = (null)}
[54248:18665109] 6--------{number = 4, name = (null)}

可以看出, 使用子类NSBlockOperation, 并调用方法addExecutionBlock:的情况下, blockOperationWithBlock:addExecutionBlock:中的操作是在不同的线程中异步执行的.

一般情况下, 如果一个NSBlockOperation对象封装了多个操作, NSBlockOperation是否开启新线程, 取决于操作的个数, 如果添加的操作的个数多, 就会自动开启线程. 开启线程的个数由系统决定.

使用自定义继承自NSOperation的子类

如果使用子类NSInvocationOperationNSBlockOperation都不能满足开发需求, 我们可以尝试使用自定义继承自NSOperation的子类. 可以通过重写mainstart方法来自定义自己的NSOperation对象, 重写main方法比较简单, 我们不需要管理操作的状态属性isExecuting, isFinished. 当main执行返回的时候, 这个操作就结束了.
一个简单的

#import 

@interface MYOperation : NSOperation

@end

#import "MYOperation.h"

@implementation MYOperation
- (void)main {
    if (!self.isCancelled) {
        NSLog(@"MyOperation -- %@", [NSThread currentThread]);
    }
}
@end

导入头文件MYOperation.h

    MYOperation *operation = [[MYOperation alloc] init];
    [operation start];

控制台打印

 MyOperation -- {number = 1, name = main}

可以看出在没有使用NSOperationQueue, 单独使用自定义继承自NSOperation的子类的情况下, 并没有开启新线程

创建队列(NSOperationQueue)

NSOperationQueue有两种队列

  • 主队列
    • 添加到主队列的操作, 都会放到主线程去执行
  • 自定义队列
    • 添加到自定义队列的操作, 都会放到子线程中执行
    • 自定义队列包含了 串行并发功能

获取主队列

[NSOperationQueue mainQueue];

自定义队列创建方法

NSOperationQueue *queue = [[NSOperationQueue alloc] init];

将操作加入队列中

NSOperation要配合NSOperationQueue来实现多线程, 那么需要将创建好的操作添加到队列中去.
两种方法:

  • - (void)addOperation:(NSOperation *)op;
    // 创建NSInvocationOperation任务对象
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"校长"];
    // 创建NSBlockOperation任务对象
    NSBlockOperation *blockOperaion = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"blockOperation -- %@", [NSThread currentThread]);
    }];
    // 创建自定义队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 将任务添加到队列中
    [queue addOperation:operation];
    [queue addOperation:blockOperaion];
- (void)task:(NSString *)name {
    // 打印当前线程
    NSLog(@"%@", [NSThread currentThread]);
    NSLog(@"%@", name);
}

控制台打印

[55782:18793884] blockOperation -- {number = 4, name = (null)}
[55782:18793886] task1 --- {number = 3, name = (null)}
[55782:18793886] task1 --- 校长

使用NSOperation子类创建操作, 并使用addOperation:将操作加入到操作队列后能够开启新线程, 进行并发执行.

  • - (void)addOperationWithBlock:(void (^)(void))block;
    addOperationWithBlock:无需创建操作, 在block中添加操作, 直接将包含操作的block加入到队列中.
    // 创建自定义队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 添加操作到队列中
    [queue addOperationWithBlock:^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        NSLog(@"4-----%@", [NSThread currentThread]);
    }];

控制台打印

[56017:18816692] 3-----{number = 5, name = (null)}
[56017:18816689] 1-----{number = 4, name = (null)}
[56017:18816690] 4-----{number = 6, name = (null)}
[56017:18816691] 2-----{number = 3, name = (null)}

addOperationWithBlock:将操作加入到操作队列后能够开启新线程, 并发执行.

控制NSOperationQueue队列的串行, 并发

设置NSOperationQueue的串行和并发, 需要设置maxConcurrentOperationCount这个属性.
maxConcurrentOperationCount: 最大并发数, 用来控制一个自定义队列中可以有多少个操作同时执行.

  • 最大操作并发数: maxConcurrentOperationCount
    • 默认为-1: 表示不进行限制, 可进行并发执行
    • 1 : 队列为串行队列, 只能串行执行
    • 大于1: 队列为并发队列, 操作并发执行 (这个值不应该超过系统限制, 即使自己设置一个很大的值, 系统也会自动调整为min)
    // 创建自定义队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 设置最大操作数为 1
    queue.maxConcurrentOperationCount = 1;
    // 添加操作到队列中
    [queue addOperationWithBlock:^{
        NSLog(@"1-----%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        NSLog(@"2-----%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        NSLog(@"3-----%@", [NSThread currentThread]);
    }];

    [queue addOperationWithBlock:^{
        NSLog(@"4-----%@", [NSThread currentThread]);
    }];

控制台打印

  • 最大操作为1的结果
[56307:18843375] 1-----{number = 3, name = (null)}
[56307:18843377] 2-----{number = 4, name = (null)}
[56307:18843377] 3-----{number = 4, name = (null)}
[56307:18843377] 4-----{number = 4, name = (null)}

可以看到, 在maxConcurrentOperationCount1时, 操作串行执行.

  • 最大操作为2的结果
[56491:18863093] 2-----{number = 4, name = (null)}
[56491:18863094] 1-----{number = 3, name = (null)}
[56491:18863092] 3-----{number = 5, name = (null)}
[56491:18863094] 4-----{number = 3, name = (null)}

可以看到, 任务不是串行执行的, 而是并发执行, 而开启线程数量是由系统决定的, 不需要我们来管理.

NSOperation操作依赖

NSOperation,NSOperationQueue最引人的地方就是它能添加操作之间的依赖关系.
通过操作依赖, 我们可以很方便的控制操作之间的执行先后顺序, NSOperation提供了3个接口供我们管理和查看依赖

  • - (void)addDependency:(NSOperation *)op;
    • 添加依赖, 使当前操作依赖于操作op的完成;
  • - (void)removeDependency:(NSOperation *)op;
    • 移除依赖, 取消当前操作对操作op的依赖
  • @property (readonly, copy) NSArray *dependencies;
    • 在当前操作开始执行之前完成执行的所有操作对象数组
操作依赖应用场景

有两个操作A操作B操作, 要求A执行完毕后, B才能执行

    // 创建自定义队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 创建操作
    NSInvocationOperation *operationA = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task:) object:@"校长"];
    // 创建操作
    NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation B%@", [NSThread currentThread]);
    }];
    // 添加依赖
    // 要求: A执行完毕, 再执行B , 那就是B依赖于A的完成
    // 调用者 : 当前操作
    // 参数 :  被依赖对象
    // 这个方法的意思就是调用者依赖于参数的完成
    [operationB addDependency:operationA];

    // 添加操作到队列中
    [queue addOperation:operationA];
    [queue addOperation:operationB];

控制台打印

[56978:18909531] task1 --- {number = 3, name = (null)}
[56978:18909531] task1 --- 校长
[56978:18909530] operation B{number = 4, name = (null)}

可以发现, 通过添加依赖, Operation A 先执行, 然后再执行Operation B

NSOperation优先级

NSOperation提供了NSOperationQueuePriority队列优先级, 用来设置同一队列中的操作优先级.

// 从低到高排列
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0, // 默认优先级
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

可以通过[operationA setQueuePriority:NSOperationQueuePriorityLow];改变操作的优先级状态.

  • 什么是就绪状态?
    • 没有需要依赖的操作等待执行的状态叫做就绪状态. 有依赖的操作都不是准备就绪状态下的操作.
    • queuePriority属性决定了进入准备就绪状态下的操作之间的执行顺序, 并且, 优先级不能取代依赖关系

NSOperation, NSOperationQueue线程间的通信

    // 创建自定义队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        // 异步执行耗时操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"异步线程---%@", [NSThread currentThread]); // 打印当前线程
        }
        // 回到主线程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"回到主线程 --- %@", [NSThread currentThread]);
        }];
    }];
    // 将操作添加到队列中
    [queue addOperation:operation];

控制台打印

[58135:19050484] 异步线程---{number = 3, name = (null)}
[58135:19050484] 异步线程---{number = 3, name = (null)}
[58135:19048720] 回到主线程 --- {number = 1, name = main}

NSOperationQueue, NSOperation常用属性和方法总结

NSOPeration常用属性和方法
  1. 取消操作方法
    • -(void)cancel; 可以取消操作, 实质是标记isCancelled状态
  2. 判断操作状态方法
    • - (BOOl)isFinished; 判断操作是否已经结束
    • - (BOOL)isCancelled; 判断操作是否已经标记为取消
    • - (BOOL)isExecuting; 判断操作是否正在运行
    • - (BOOL)isReady; 判断操作是否处于准备就绪状态, 这个值和操作的依赖关系相关
  3. 操作同步
    • - (void)waitUntilFinished;阻塞当前线程, 直到该操作结束, 可用于线程执行顺序的同步.
    • - (void)setCompletionBlock:(void (^)(void))block;, completionBlock会在当前操作执行完毕时执行completionBlock
    • - (void)addDependency:(NSOperation *)op; 添加依赖
    • - (void)removeDependency:(NSOperation *)op; 移除依赖
    • @property (readonly, copy) NSArray *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组.
NSOperationQueue常用属性和方法
  1. 取消 暂停 恢复操作
    • - (void)cancelAllOperation; 取消队列内的所有操作
    • - (BOOL)isSuspended; 判断队列是否处于暂停状态, YES为暂停状态, NO为恢复状态
    • - (void)setSuspendes:(BOOL)b; 可设置操作的暂停和回复, YES代表暂停队列, NO代表恢复队列
  2. 操作同步
    • - (void)waitUntilAllOperationAreFinished; 阻塞当前线程, 直到队列中的操作全部执行完毕.
  3. 添加/获取操作
    • - (void_addOperationWithBlock:(void (^)(void))block;向队列中添加一个NSBlockOperation类型操作对象
    • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向队列中添加操作数组, wait标志是否阻塞当前线程直到所有操作结束
    • - (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除).
    • - (NSUInteger)operationCount; 当前队列中的操作数
  4. 获取队列
    • + (id)currentQueue; 获取当前队列, 如果当前线程不是在NSOperationQueue上运行则返回nil.
    • + (id)mainQueue; 获取主队列
问题 - 线程和队列又有什么区别

简单来说, 队列就是用来存放任务暂存区, 而线程是执行任务的路径, GCD将这些存在于队列任务取出来放到相应的线程上去执行, 而队列的性质决定了在其中的任务在哪种线程上执行.

iOS多线程_第2张图片
15353404897575.png

你可能感兴趣的:(iOS多线程)