多线程学习笔记

2019-02-20  本文已影响0人  顺7zi燃

1 概念

1.1 什么是多线程

1个进行中可以开发多个线程,多个线程可以“同时”执行不同的任务;可以解决程序阻塞的问题;多线程可以提高程序的执行效率

1.2 多线程执行原理

(单核CPU) 同一时间,cpu只能处理1个线程,只有1个线程在执行
多线程同时执行:是CPU快速的在多个线程之间的切换
cpu调度线程的时间足够快,就造成了多线程的“同时”执行
如果线程数非常多,cpu会在n个线程之间切换,消耗大量的cpu资源 (一般开3-6个线程)
每个线程调度的次数会降低,线程的执行效率降低

1.3 多线程优/缺点

优点:

能适当提高程序的执行效率
能适当提高资源的利用率(cpu,内存)
线程上的任务执行完成后,线程会自动销毁

缺点:

开启线程需要占用一定的内存空间(默认情况下,每一个线程都占 512KB)
如果开启大量的线程,会占用大量的内存空间,降低程序的性能
线程越多, cpu 在调用线程上的开销就越大
程序设计更加复杂,比如线程间的通信、多线程的数据共享

1.4 主线程

主线程:一个程序运行后,默认会开启1个线程,称为“主线程” 或 “UI线程”;主线程一般用来 刷新UI界面,处理UI事件(比如: 点击、滚动、拖拽等事件),主要处理与用户的交互。

主线程使用注意:别将耗时的操作放到主线程中;耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种卡的坏体验

1.5 iOS 同步/异步执行

  • 同步执行:比如dispatch_sync,这个函数会把一个 block 加入到指定的队列中,而且会一直等到执行完 block,这个函数才返回。因此在 block 执行完之前,调用 dispatch_sync 方法的线程是阻塞的。
  • 异步执行:比如dispatcy_async,这个函数会把一个 block 加入到指定的队列中,但是和同步执行不同的是,这个函数把 block 加入队列后不等 block 的执行就立刻返回了。

注意:

  • dispatch_async 和 dispatch_sync 作用是将 block 添加进指定的队列中。并根据是否为 sync 决定调用该函数的线程是否需要阻塞。
  • 这里调用该函数的线程并不一定执行 block块,任务的执行者是 GCD 分配给任务所在队列的线程
  • 结论:调用 dispatch_sync 和 dispatch_async 的线程,并不一定是 block 的执行者。

1.6 iOS 串行/并发队列

  • 串行队列:比如 dispatch_get_main_queue,这个队列中所有任务,一定按照FIFO(先进先出)执行。不仅如此,还可以保证在执行某个任务时,在它前面进入队列的所有任务都已经执行完成。对于每一个不同的串行队列,系统会为这个队列建立唯一的线程来执行代码
  • 并行队列:比如 dispatch_get_global_queue,这个队列中的任务是按照FIFO(先进先出)开始执行,注意是开始,但是它们的执行结束时间是不确定的,取决于每个任务的耗时。并发队列中的任务:GCD会动态分配多条线程来执行。具体几条线程取决于当前内存使用状况,线程池中线程数等因素。

1.7 iOS 中多线程的技术方案

image

2 pthread

2.1 pthread 方法

int pthread_create(pthread_t _Nullable * _Nonnull __restrict,const pthread_attr_t * _Nullable __restrict,void * _Nullable (* _Nonnull)(void * _Nullable),void * _Nullable __restrict);

参数:

  1. 指向线程标识符的指针,C 语言中类型的结尾通常 _t/Ref,而且不需要使用 *
  2. 用来设置线程属性
  3. 线程运行函数的起始地址
  4. 运行函数的参数

返回值:

  • 若线程创建成功,则返回0
  • 若线程创建失败,则返回出错编号

2.2 __bridge 桥接

__bridge 桥接 用于告诉编译器如何管理内存

3 NSThread

创建

方式1:NSThread *thread = [NSThread alloc] initWithTarget ….. 创建线程
[thread start]; 执行线程
方式2:[NSThread detachNewThreadSelector…….] // 创建并执行
方式3:[ self performSelectorInBackground……] // 创建并执行

线程的状态

image

4 GCD

4.1 Dispatch Queue 执行处理的等待队列

两种 Dispatch Queue:

Serial Dispatch Queue 等待现在执行中的处理
Concurrent Dispatch Queue 不等待现在执行中的处理

4.2 创建 Dispatch Queue

两种方式:

第一种:dispatch_queue_create
第二种:获取系统提供的 Dispatch Queue
dispatch_get_main_queue()
dispatch_get_global_queue(0, 0)

4.3 dispatch_set_target_queue 修改优先级

优先级:
DISPATCH_QUEUE_PRIORITY_HIGH : 高
DISPATCH_QUEUE_PRIORITY_DEFAULT : 默认
DISPATCH_QUEUE_PRIORITY_LOW : 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND :后台

4.4 dispatch_after 指定时间后追加处理到 Dispatch Queue

与 dispatch_time 结合使用

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
    });
    // 合并为一条语句
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    });

4.5 Dispatch Group 线程组

用于:追加到 Dispatch Queue 中的多个处理全部结束后想执行结束处理

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"blk0"); });
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"blk1"); });
    // 使用 Dispatch Group 可监视这些处理执行的结束,一旦检测到所有处理执行结束,就可将结束的处理追加到 Dispatch Queue中
    // dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done"); });
    
    // 也可使用
    // dispatch_group_wait :仅等待全部处理执行结束,第二个参数 等待的时间(dispatch_time_t 类型 ; DISPATCH_TIME_FOREVER :永久等待)
    // 返回结果:0:全部处理执行结束。 非0:经过了指定的时间,但属于 Dispatch Group 的某个处理还在执行中
    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

4.6 dispatch_barrier_async

使用 dispatch_barrier_async 添加的 block 会在之前添加的 block 全部运行结束之后,才在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!

    dispatch_async(queue, blk0_for_reading);
    dispatch_async(queue, blk1_for_reading);
    dispatch_barrier_async(queue, blk_for_writing);
    dispatch_async(queue, blk2_for_reading);
    dispatch_async(queue, blk3_for_reading);

4.7 dispatch_sync 同步

将指定的 Block “同步”追加到指定的 Dispatch Queue中。在追加 Block 结束之前, dispatch_sync 函数会一直等待。

使用场景:

例:执行 Main Dispatch Queue 时,使用另外的线程 Global Dispatch Queue 进行处理,处理结束后立即使用所得到的结果。这种情况就要使用 dispatch_sync函数。

注意事项:

dispatcy_sync 是简易版的 dispatch_group_wait ,dispatcy_sync 容易引起死锁。

死锁代码:

    // 1种:在主线程执行 该句,会造成线程死锁
    // 在主线程 中执行指定的 Block, 并等待其执行结束。而其实 在主线程中下在执行这些源代码,所以无法执行追加到 主线程 的 Block
    dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"Hello"); });
    // 2种:在主线程执行 该句,会造成线程死锁
    dispatch_async(dispatch_get_main_queue(), ^{
        dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"Hello");});
    });
    // 3种:Serial Dispatch Queue 也会引起死锁
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.serialdispatchQueue", NULL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{NSLog(@"Hello"); });
    });
    // 4种:
   // dispatch_barrier_sync 函数的作用是在等待追加的处理全部执行结束后,再追加处理到 Dispatch Queue 中,此外,它还与 dispatch_sync 函数相同,会等待追加处理的t执行结束

4.8 dispatch_apple

dispatch_apple 函数是 dispatch_sync 函数和 Dispatch Group 的关联 API。 该函数按指定的次数将指定的 Block 追加到指定的 Dispatch Queue 中,并等待全部处理执行结束。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply([array count], queue, ^(size_t index) {
        NSLog(@"%zu: %@", index, [array objectAtIndex:index]);
    });
    // 最后输出的 done
    NSLog(@"done");

4.9 dispatch_suspend / dispatch_resume

dispatch_suspend 函数 挂起指定的 Dispatch Queue
dispatch_resume 函数 恢复指定的 Dispatch Queue

4.10 Dispatch Semaphore

4.11 dispatch_once

保证在应用程序执行中只执行一次指定处理。一般用于 生成单例模式对象时使用

4.12 Dispatch I/O

5 NSOperation

5.1 NSOperation 简介

  1. 作用:配合使用 NSOperation 和 NSOperationQueue 能实现多线程编辑
  2. 具体步骤:
    (1)先将需要执行的操作封装到一个 NSOperation 对象中
    (2)然后将 NSOperation 对象添加到 NSOperationQueue 中
    (3)系统会自动将 NSOperationQueue 中的 NSOperation 取出来
    (4)将取出的 NSOperation 封装的操作放到一条新线程中执行
  3. NSOperation 的子类
  • NSInvocationOption
  • NSBlockOperation
  • 自定义子类继承 NSOperation 实现内部相应的方法
  1. NSOperationQueue
    NSOperationQueue的作⽤:NSOperation可以调⽤start⽅法来执⾏任务,但默认是同步执行的
    如果将NSOperation添加到NSOperationQueue(操作队列)中,系统会自动异步执行NSOperation中的操作
    添加操作到NSOperationQueue中,自动执行操作,自动开启线程

5.2 NSInvocationOperation

    // NSOperation 中 : 操作 -> 异步执行的任务;  队列 -> 全局队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
    //将操作添加到队列,会"异步"执行 selector 方法
    // [queue addOperation:op];

    // start方法 会在当前线程执行 @selector 方法
    [op start];

5.3 NSBlockOperation

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 第 1 种
    NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"thread = %@", [NSThread currentThread]);
    }];
    [queue addOperation:blockOp];
    // 第 2 种 直接添加block
    [queue addOperationWithBlock:^{
        NSLog(@"thread = %@", [NSThread currentThread]);
    }];
// addExecutionBlock
    //NSBlockOperation创建时block中的任务是在主线程执行,而运用addExecutionBlock加入的任务是在子线程执行的
    NSBlockOperation *blockOp = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"thread = %@", [NSThread currentThread]);
    }];
    [blockOp addExecutionBlock:^{
        NSLog(@"1 execution thread = %@", [NSThread currentThread]);
    }];
    [blockOp addExecutionBlock:^{
        NSLog(@"2 execution thread = %@", [NSThread currentThread]);
    }];
    [blockOp start];

线程间通讯

    NSOperationQueue *q = [[NSOperationQueue alloc] init];
    [q addOperationWithBlock:^{
        NSLog(@"耗时操作 %@", [NSThread currentThread]);
        // 主线程更新 UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"更新 UI %@", [NSThread currentThread]);
        }];
    }];

5.4 最大并发操作数

并发数:同时执行的任务数
最大并发数:同一时间最多只能执行的任务的个数
注意:如果没有设置最大并发数,那么并发数的个数是由系统内存和CPU决定。最大并发数一般以 2~3为宜。

// 设置最大并发数
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//    queue.maxConcurrentOperationCount = 2;
    [queue setMaxConcurrentOperationCount:2];

5.5 暂停 & 继续

  • 队列挂起,当前“没有完成的操作”,是包含在队列的操作数中的
  • 队列挂起,不会影响已经执行操作的执行状态
  • 队列一旦被挂起,再添加的操作不会被调度
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 暂停和恢复 YES:暂停队列 NO:恢复队列
    queue.suspended = !queue.suspended;
    // 当前状态
    BOOL b = queue.isSuspended;

5.6 取消全部操作

  • 取消队列中所有的操作
  • 不会取消正在执行中的操作
  • 不会影响队列的挂起状态
    // 1. 取消所有操作
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    // 取消对列中的所有操作,同样不会影响到正在执行中的操作!
    [queue cancelAllOperations];
    
    // 2. 取消单个操作
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
    [queue addOperation:op];
    [op cancel];

5.6 操作优先级

优先级的取值: 优先级高的任务,调用的几率会更大
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8

    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"queue"];
    // 设置操作的优先级
    [op setQueuePriority:NSOperationQueuePriorityLow];

5.7 依赖关系 addDependency

   NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"登录 %@", [NSThread currentThread]);
   }];
   NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"付费 %@", [NSThread currentThread]);
   }];
   NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"下载 %@", [NSThread currentThread]);
   }];
   NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"通知用户 %@", [NSThread currentThread]);
   }];
   // 通过设置依赖来保证执行顺序
   [op2 addDependency:op1];
   [op3 addDependency:op2];
   [op4 addDependency:op3];
   // 注意不要循环依赖
   //    [op1 addDependency:op4];
   
   NSOperationQueue *queue = [[NSOperationQueue alloc] init];
   // queue 队列是否阻塞当前线程
   [queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
   [[NSOperationQueue mainQueue] addOperation:op4];
   
   NSLog(@"come here");

6 NSOperation 与 GCD 的对比

  1. 将 block 添加到队列(串行/并发/主队列),并且指定任务执行的函数(同步/异步)
  2. GCD 是底层的 C 语言构成的 API
  3. 在队列中执行的是由 block 构成的任务,这是一个轻量级的数据结构
  4. 要停止已经加入 queue 的 block 需要写复杂的代码
  5. 需要通过 Barrier 或者同步任务设置任务之间的依赖关系
  6. 只能设置队列的优先级
  7. 高级功能:
    一次性 once
    延迟操作 after
    调度组
  1. 核心概念:把 操作(异步) 添加到 队列(全局的并发队列)
  2. OC 框架,更加面向对象,是对 GCD 的封装
  3. Operation 作为一个对象,为我们提供了更多的选择
  4. 可以随时取消已经设定要准备执行的任务,已经执行的除外
  5. 可以跨队列设置操作的依赖关系
  6. 可以设置队列中每一个操作的优先级
  7. 高级功能:
    最大操作并发数(GCD不好做)
    继续/暂停/全部取消
    跨队列设置操作的依赖关系
上一篇下一篇

猜你喜欢

热点阅读