美文网首页
多线程学习笔记

多线程学习笔记

作者: 顺7zi燃 | 来源:发表于2019-02-20 19:35 被阅读0次

    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 的对比

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

    相关文章

      网友评论

          本文标题:多线程学习笔记

          本文链接:https://www.haomeiwen.com/subject/ldtkyqtx.html