美文网首页
GCD基础回顾,大神也不敢说全都记得

GCD基础回顾,大神也不敢说全都记得

作者: 褪而未变 | 来源:发表于2017-07-22 19:47 被阅读0次

    1.GCD简介

    什么是GCD

    • 全称是Grand Central Dispatch
    • 纯C语言的,提供了非常多强大的函数.

    GCD的优势

    • GCD是苹果公司为多核的并行运算提出的解决方案.
    • GCD会自动利用更多的CPU内核(比如双核、四核)
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程).
    • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码.

    GCD的核心

    • 将任务添加到队列
    • 任务 : 执行什么操作
    • 队列 : 用来存放任务

    GCD使用的两个步骤

    • 创建任务 : 确定要做的事情
      • GCD中的任务是使用BLOCK封装的.
    • 将任务添加到队列中
      • GCD会自动将队列中的任务取出,放到对应的线程中执行.
      • 任务的取出遵循队列的FIFO原则 : 先进先出,后进后出.

    2.GCD的核心及基本语法

    • 将任务添加到队列
    • 任务 : 执行什么操作
    • 队列 : 用来存放任务

    任务添加到队列分开写

    /// 队列+任务
    - (void)gcdDemo1
    {
        // 全局并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        // 任务
        void (^task)() = ^ {
            NSLog(@"%@",[NSThread currentThread]);
        };
    
        // 同步任务
    //    dispatch_sync(queue, task);
    
        // 异步任务 : 每次执行任务的线程不一定是一样的
        dispatch_async(queue, task);
    
        NSLog(@"end");
    }
    

    简写

    - (void)gcdDemo2
    {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
    }
    

    线程间通信

    /// 线程间通信
    - (void)gcdDemo3
    {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"下载中... %@",[NSThread currentThread]);
    
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"下载完成 %@",[NSThread currentThread]);
            });
        });
    }
    

    使用GCD的线程间通信实现异步下载网络图片

    - (void)downloadImage
    {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            // 获取网络图片地址
            NSURL *url = [NSURL URLWithString:@"http://photocdn.sohu.com/20151209/mp47379110_1449633737507_2_th.png"];
            // 获取网络图片二进制数据
            NSData *data = [NSData dataWithContentsOfURL:url];
            // 获取图片对象
            UIImage *image = [UIImage imageWithData:data];
    
            // 图片下载完成之后,回到主线程更新UI
            dispatch_async(dispatch_get_main_queue(), ^{
                // 设置图片视图
                self.imageView.image = image;
                // 图片视图自适应图片的大小
                [self.imageView sizeToFit];
                // 设置滚动视图
                [self.scrollView setContentSize:image.size];
            });
        });
    }
    

    3.GCD 与 NSThread 的对比

    • 所有的代码写在一起的,让代码更加简单,易于阅读和维护
    • NSThread 通过 @selector 指定要执行的方法,代码分散
    • GCD 通过 block 指定要执行的代码,代码集中
    • 使用 GCD 不需要管理线程的创建/销毁/复用的过程.程序员不用关心线程的生命周期
    • 如果要开多个线程 NSThread 必须实例化多个线程对象
    • NSThread 靠 NSObject 的分类方法实现的线程间通讯,GCD 靠 block

    4.GCD中的任务

    • GCD中有2个用来执行任务的函数 :

    • 同步的方式执行任务 : 在当前线程依次执行任务

    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    
    * queue:队列
    * block:任务
    
    • 异步的方式执行任务 : 新开线程,在新线程中执行任务
    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    
    * queue:队列
    * block:任务
    

    5.GCD的队列

    (1)串行队列(Serial Dispatch Queue)

    * 让任务一个接着一个`有序的执行`:不管队列里面放的是什么任务.一个任务执行完毕后,再执行下一个任务.
    * 同时只能调度一个任务执行.
    

    特点

    • 以先进先出的方式,顺序调度队列中的任务执行
    • 无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务

    队列创建

    // 参数1 : 队列的标示符
    // 参数2 : 队列的属性.决定了队列是串行的还是并行的.
    dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);
    

    串行队列+同步任务

    #pragma mark - 串行队列+同步任务
    /*
        1. 没有开新线程
        2. 循环是顺序打印
        3. @"end"最后执行
     */
    - (void)gcdDemo1
    {
        // 串行队列
        dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);
    
        for (int i = 0; i < 10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%d %@",i,[NSThread currentThread]);
            });
        }
    
        NSLog(@"end");
    }
    

    串行队列+异步任务

    #pragma mark - 串行队列+异步任务
    /*
        1. 开一条新线程 : 因为队列是顺序调度任务,前一个任务执行完成以后才能调度后面的任务,开一条新线程就够了
        2. 循环是顺序打印
        3. @"end"不是在最后执行
     */
    - (void)gcdDemo2
    {
        // 串行队列
        dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_SERIAL);
    
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%d %@",i,[NSThread currentThread]);
            });
        }
    
        NSLog(@"end");
    }
    

    (2)并发队列(Concurrent Dispatch Queue)

    * 可以让多个任务`并发/同时`执行.自动开启多个线程同时执行多个任务.
    * 同时可以调度多个任务执行
    * 并发队列的并发功能只有内部的任务是异步任务时,才有效.
    
    ## 特点
    
    • 以先进先出的方式,并发调度队列中的任务执行
    • 如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务
    • 如果当前调度的任务是异步执行的,会调度多个线程同时执行多个任务.

    队列创建

    // 参数1 : 队列的标示符
    // 参数2 : 队列的属性.决定了队列是串行的还是并行的.
    dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);
    

    并发队列+同步任务

    #pragma mark - 并发队列+同步任务
    /*
        1. 没有开新线程
        2. 循环顺序打印
        3. @"end"最后执行
     */
    - (void)gcdDemo1
    {
        // 并发队列
        dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);
    
        for (int i = 0; i < 10; i++) {
            dispatch_sync(queue, ^{
                NSLog(@"%d %@",i,[NSThread currentThread]);
            });
        }
    
        NSLog(@"end");
    }
    

    并发队列+异步任务

    #pragma mark - 并发队列+同步任务
    /*
     1. 开启多条新线程
     2. 循环无序打印
     3. @"end"不是最后执行
     */
    - (void)gcdDemo2
    {
        // 并发队列
        dispatch_queue_t queue = dispatch_queue_create("zj", DISPATCH_QUEUE_CONCURRENT);
    
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%d %@",i,[NSThread currentThread]);
            });
        }
    
        NSLog(@"end");
    }
    

    (3)主队列

    特点

    • 专门用来在主线程上调度任务的队列.
    • 不会开启新线程.
    • 以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行.
    • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度.

    主队列创建

    • 主队列是负责在主线程调度任务的,会随着程序启动一起创建,只需要获取不用创建.
    dispatch_queue_t queue = dispatch_get_main_queue();
    

    主队列+异步任务

    #pragma mark - 主队列+异步任务
    /*
        1. 执行顺序 : @"start"->@"end"->执行中...
        2. 没有开启新线程
     */
    - (void)gcdDemo1
    {
        // 主队列 : 程序一启动就创建好的,不需要创建
        dispatch_queue_t queue = dispatch_get_main_queue();
    
        NSLog(@"start");
    
        dispatch_async(queue, ^{
            NSLog(@"执行中...%@",[NSThread currentThread]);
        });
    
        NSLog(@"end");
    }
    

    主队列+同步任务=死锁

    #pragma mark - 主队列+同步任务=死锁
    /*
        1. 执行顺序 : @"start"   后面的任务被阻塞了
        2. 同步任务和主线程相互等待,造成线程死锁
     */
    - (void)gcdDemo2
    {
        dispatch_queue_t queue = dispatch_get_main_queue();
    
        NSLog(@"start");
    
        dispatch_sync(queue, ^{
            NSLog(@"执行中...%@",[NSThread currentThread]);
        });
    
        NSLog(@"end");
    }
    

    死锁解决办法

    #pragma mark - 死锁解决办法
    // 主队列中的同步任务放进子线程中,不使其阻塞主线程
    - (void)gcdDemo3
    {
        dispatch_async(dispatch_queue_create("ZJ", DISPATCH_QUEUE_CONCURRENT), ^{
    
            NSLog(@"start");
    
            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"执行中...%@",[NSThread currentThread]);
            });
    
            NSLog(@"end");
        });
    }
    

    (4)全局队列

    全局队列 & 并发队列的区别

    • 全局队列又叫全局并发队列.
    • 是系统为了方便程序员开发提供的,其工作表现与并发队列一致
    • 全局队列
      • 没有名称
      • 无论 MRC & ARC 都不需要考虑释放
      • 日常开发中,建议使用"全局队列"
    • 并发队列
      • 有名字,和 NSThreadname 属性作用类似
      • 如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象
      • 开发第三方框架时,建议使用并发队列

    全局队列+异步任务

    • 运行效果和并发队列一样
    #pragma mark - 全局队列
    // 执行效果跟并发队列一样的
    - (void)gcdDemo
    {
        // 全局队列,跟主队列一样不需要创建
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        for (int i = 0; i < 10; i++) {
            dispatch_async(queue, ^{
                NSLog(@"%d %@",i,[NSThread currentThread]);
            });
    
            /*
            dispatch_sync(queue, ^{
                NSLog(@"%d %@",i,[NSThread currentThread]);
            });
            */
        }
    
        NSLog(@"end");
    }
    

    参数

    1. 服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

      • iOS 8.0及以后
        • QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
        • QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
        • QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)
        • QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!)
        • QOS_CLASS_BACKGROUND 0x09, 后台
        • QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配
      • iOS 7.0及以前
        • DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
        • DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
        • DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
        • DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
    2. 为未来保留使用的,应该永远传入0

    结论:如果要适配 iOS 7.0 & 8.0,使用以下代码:
    dispatch_get_global_queue(0, 0);

    (5)GCD队列和任务组合总结

    • 同步和异步决定了要不要开启新的线程 (同步不开,异步开)

      • 同步:在当前线程中执行任务,不具备开启新线程的能力
      • 异步:在新的线程中执行任务,具备开启新线程的能力
    • 串行和并发决定了任务的执行方式

      • 串行:一个任务执行完毕后,再执行下一个任务
      • 并发:多个任务并发(同时)执行
    • 当任务是异步的时候,队列决定了开启多少条线程

      • 串行队列 : 只开一条
      • 并发队列 : 可以开启多条

    (6)GCD同步任务的作用

    建立依赖关系

    • 使用同步任务来建立任务之间的依赖关系
    - (void)GCDDemo1
    {
        // 创建全局的并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        dispatch_sync(queue, ^{
            // 查看当前线程
            NSLog(@"登陆 %@",[NSThread currentThread]);
        });
    
        dispatch_sync(queue, ^{
            // 查看当前线程
            NSLog(@"付费 %@",[NSThread currentThread]);
        });
    
        dispatch_sync(queue, ^{
            // 查看当前线程
            NSLog(@"下载 %@",[NSThread currentThread]);
        });
    
        dispatch_async(dispatch_get_main_queue(), ^{
            // 查看当前线程
            NSLog(@"通知用户 %@",[NSThread currentThread]);
        });
    }
    
    • 问题 : 依赖关系虽然有了,但是耗时的网络操作都在主线程中执行的.需要优化.

    依赖关系的优化

    • 使同步任务在子线程中建立依赖关系
    - (void)GCDDemo2
    {
        // 创建全局的并发队列
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        dispatch_async(queue, ^{
    
            dispatch_sync(queue, ^{
                // 查看当前线程
                NSLog(@"登陆 %@",[NSThread currentThread]);
            });
    
            dispatch_sync(queue, ^{
                // 查看当前线程
                NSLog(@"付费 %@",[NSThread currentThread]);
            });
    
            dispatch_sync(queue, ^{
                // 查看当前线程
                NSLog(@"下载 %@",[NSThread currentThread]);
            });
    
            dispatch_async(dispatch_get_main_queue(), ^{
                // 查看当前线程
                NSLog(@"通知用户 %@",[NSThread currentThread]);
            });
        });
    }
    

    5.GCD延迟执行(after)

    • 延迟操作 : 'dispatch_after'这个函数默认是异步执行
    - (void)afterDemo1
    {
        NSLog(@"开始");
    
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
            // 延迟执行的代码
            NSLog(@"我来晚了吗?");
        });
    
        NSLog(@"结束");
    }
    
    • 函数拆解
    - (void)afterDemo2
    {
        NSLog(@"开始");
    
        /*
        参数1 : dispatch_time_t when,表示延迟的时间
        参数2 : dispatch_queue_t queue,表示任务执行的队列
        参数3 : dispatch_block_t block,表示线程要执行的任务
        */
    
        // 参数1 : 延迟多长时间,精确到纳秒
        dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
    
        // 参数2 : 在哪个队列
    //    dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        // 参数3 : 执行哪个任务
        dispatch_block_t block = ^{
            // 延迟执行的代码
            NSLog(@"我来晚了吗? %@",[NSThread currentThread]);
        };
    
        // 延迟多少纳秒,在哪个队列中调度执行哪个任务
        dispatch_after(when, queue, block);
    
        NSLog(@"结束");
    }
    

    6.GCD一次性执行(once)

    • dispatch_once_t内部有一把锁,是能够保证线程安全.而且是苹果公司推荐使用的.
    • 在开发中,有些代码只想就只执行一次.典型的应用场景就是设计单例模式.

    验证一次性执行的可靠性

    - (void)onceDemo1
    {
        NSLog(@"mark");
    
        static dispatch_once_t onceToken;
    
        NSLog(@"返回值 %ld",onceToken);
    
        dispatch_once(&onceToken, ^{
            NSLog(@"hello");
        });
    }
    
    • 原理 : onceToken有个初始值,当第一次执行时,判断是否是初始值,如果是初始值就执行函数内部的代码,执行结束之前会修改onceToken初始值.反之,就不执行.

    异步并发任务中验证执行一次代码的安全性

    - (void)onceDemo2
    {
        dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
        // 多线程环境下测试安全性
        for (int i = 0; i < 1000; i++) {
    
            NSLog(@"%d",i);
    
            dispatch_async(queue, ^{
    
                static dispatch_once_t onceToken;
                dispatch_once(&onceToken, ^{
                    NSLog(@"hello");
                });
            });
        }
    }
    

    7.单例设计模式

    单例设计模式的特点

    1. 有一个全局访问点 (供全局实例化单例的类方法)
    2. 单例保存在静态存储区
    3. 在内存有且只有一份
    4. 生命周期跟APP一样长

    懒汉式单例

    所谓懒汉式单例,表示在使用时才会创建

    + (instancetype)sharedNetworkTool
    {
        // 保存在静态存储区
        static id instance;
    
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
    
            instance = [[self alloc] init];
        });
    
        return instance;
    }
    

    饿汉式单例

    所谓饿汉式单例,表示尽早地创建单例实例,可以利用 initialize 方法建立单例

    • initialize 会在类第一次被使用时调用
    • initialize 方法的调用是线程安全的
    @implementation NetworkManager
    
    // 声明静态对象
    static id instance;
    
    + (void)initialize
    {
        // 只会开辟一次内存空间,只会被实例化一次
        instance = [[self alloc] init];
    }
    
    // 饿汉式单例
    + (instancetype)sharedManager
    {
        return instance;
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:GCD基础回顾,大神也不敢说全都记得

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