GCD

作者: fanzq_jianshu | 来源:发表于2016-05-15 20:15 被阅读39次

~ 写在正文之前:文章转移到翻这个墙中,希望继续关注啦。(2017.11.5)

(1) GCD基本概念

01 GCD
    1.1 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”
纯C语言,提供了非常多强大的函数

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

02 两个核心概念-队列和任务
    2.1 任务:需要执行的操作;任务是最小单位,任务一旦执行,就必须执行完毕;线程不同,线程可以暂停,阻塞,强制死亡。
    2.2 队列:用来存放任务,任务的取出遵循队列的FIFO原则:先进先出,后进后出
    2.3 任务、队列、线程、同异步函数的关系:
        2.3.1任务:一个具体的任务(操作)
        2.3.2队列:任务列表及执行顺序(并发队列:无序执行;串行队列:顺序执行)
        2.3.3线程:车间工人,执行任务的个人(实际由CPU处理)
        2.3.4同步与异步函数:决定执行任务的总人数
        2.3.5总结:按什么顺序由多少人(线程)执行多少任务。

03 同步和异步(影响线程数)
    3.1 同步:只能在当前线程中执行任务,不具备开启新线程的能力;在当前线程执行并且在所在队列中马上执行(重要)
    3.2 异步:可以在新的线程中执行任务,具备开启新线程的能力(具备不代表一定开启新的线程),不要求马上执行

04 GCD的队列类型(影响执行顺序)
    4.1 并发队列(Concurrent Dispatch Queue)
        4.1.1 自动开启多个线程同时执行任务
        4.1.2 并发功能只有在异步函数下才有效
    4.2 串行队列(Serial Dispatch Queue)
        4.2.1 一个任务执行完毕后,再执行下一个任务
    4.3 主队列(跟主线程相关联的队列)
        4.3.1 主队列是GCD自带的一种特殊的串行队列
        4.3.2 在主队列中的任务,都会在主线程中执行
        4.3.3 主线程主队列执行的任务中存在同步函数+主队列任务,会导致死锁(因为主队列中的当前任务并没有完成,它的下一个任务是新增的同步函数+主队列任务,并且要求所在线程立即执行所在队列(所在队列不是当前队列),系统无法在队列中的当前任务没处理完成前切换任务,导致死锁)

05 线程执行组合:
    01 异步函数+并发队列:开启多条线程,并发执行任务
    02 异步函数+串行队列:开启一条线程,串行执行任务
    03 同步函数+并发队列:不开线程,串行执行任务
    04 同步函数+串行队列:不开线程,串行执行任务
    05 异步函数+主队列:不开线程,在主线程中串行执行任务
    06 同步函数+主队列:不开线程,在主线程中串行执行任务(注意死锁发生)
    07 使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列

(2)GCD基本使用【重点】

  • 步骤(以什么顺序由多少人(线程)执行多少任务):
    • 创建或获取队列

    • 通过同步函数或异步函数或函数方式将任务添加到队列中

01 同步函数与异步函数
    1.1 用同步(sync)的函数执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务
    1.2 用异步(async)的函数执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务
    1.3 通过函数的方式来封装任务
    /*
     第一个参数:队列
     第二个参数:要调用函数需要传递的参数
     第三个参数:函数
     */
    NSString *str = @"wendingding";
    //桥接--(__)
    dispatch_async_f(queue, (__bridge void *)(str), run);

02 并发队列与串行队列
    2.1 手动创建并发队列
    /*
     第一个参数:C语言的字符串 队列名称
     第二个参数:队列的类型
     DISPATCH_QUEUE_CONCURRENT--并发
     DISPATCH_QUEUE_SERIAL---串行队列
     */
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", DISPATCH_QUEUE_CONCURRENT);

    2.2 获得全局并发队列
        2.2.1GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建
    /*
     第一个参数:队列的优先级
     第二个参数:此参数是留给未来使用,暂时无用,用0即可
     DISPATCH_QUEUE_CONCURRENT--并发
     DISPATCH_QUEUE_SERIAL---串行队列
     */
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        2.2.2 全局并发队列的优先级
#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 // 后台

    2.3 串行队列
    /*
     第一个参数:C语言的字符串 队列名称
     第二个参数:队列的类型
     DISPATCH_QUEUE_CONCURRENT--并发
     DISPATCH_QUEUE_SERIAL---串行队列或传递NULL
     */
dispatch_queue_t queue = dispatch_queue_create("com.520it.queue", NULL);

    2.4 使用主队列(跟主线程相关联的队列)
//使用dispatch_get_main_queue()获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();

(3)GCD线程间通信

 //0.获取一个全局的队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    //1.先开启一个线程,把下载图片的操作放在子线程中处理
    dispatch_async(queue, ^{

       //2.下载图片
        NSURL *url = [NSURL URLWithString:@"http://h.hiphotos.baidu.com/zhidao/pic/item/6a63f6246b600c3320b14bb3184c510fd8f9a185.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];

        NSLog(@"下载操作所在的线程--%@",[NSThread currentThread]);

        //3.回到主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
           self.imageView.image = image;
           //打印查看当前线程
            NSLog(@"刷新UI---%@",[NSThread currentThread]);
        });

    });

(4)GCD其它常用函数


    01 栅栏函数(控制任务的执行顺序)
    dispatch_barrier_sync(queue, ^{
        NSLog(@"--dispatch_barrier_sync-");
    });
//在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
//这个queue不能是全局的并发队列,苹果文档没做解释


    02 延迟执行(延迟·控制在哪个线程执行)
        2.1 第一种方法——使用GCD函数
      dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"---%@",[NSThread currentThread]);
    });

        2.2 第二种方法——调用NSObject的方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// 2秒后再调用self的run方法

        2.3 第三种方法——使用NSTimer
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO];


    03 一次性代码(注意不能用在懒加载)
    -(void)once
    {
        //整个程序运行过程中只执行一次,默认是线程安全的
        //onceToken用来记录该部分的代码是否被执行过
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{

            NSLog(@"-----");
        });
    }

    04 快速迭代(开多个线程并发完成迭代操作)
       dispatch_apply(subpaths.count, queue, ^(size_t index) {
       // index顺序不确定
       // 内部开多个线程并发完成迭代操作,有可能包括主线程
       // 虽然是多线程,但一样会卡住线程
       // 应用场景:快速处理某些不算太耗时的操作,并回到主线程进行某些操作。
       // 如果处理耗时操作,建议使用并发+异步函数,手动回到主线程刷新,不建议使用这种方法
    });

    05 队列组(同栅栏函数)
    //创建队列组
    dispatch_group_t group = dispatch_group_create();
    //多次执行耗时的异步操作
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});
    //队列组中的任务执行完毕之后,执行该函数
    dispatch_group_notify(dispatch_group_t group,
    dispatch_queue_t queue,
    dispatch_block_t block);

(5)快速迭代剪切文件

-(void)apply
{
    //1.创建文件管理者
    NSFileManager *manager = [NSFileManager defaultManager];
    NSString *from = @"/Users/a1/Desktop/form";
    NSString *to = @"/Users/a1/Desktop/to";

    //2.获得要剪切的所有文件
    NSArray *subPaths =[manager subpathsAtPath:from];
    NSLog(@"%@",subPaths);

    NSInteger count = subPaths.count;

    //3.创建并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    // 4.快速迭代
    // 内部不能使用continue
    /*
     第一个参数:循环的次数
     第二个参数:队列
     第三个参数:block 在里面执行迭代任务  index:索引
     */
    dispatch_apply(count, queue, ^(size_t index) {
        //剪切图片
        //4.1获得文件名称
        NSString *fileName = subPaths[index];

        //4.2 拼接要剪切的文件的全路径
        NSString *fullPath1 = [from stringByAppendingPathComponent:fileName];

        //4.3 拼接文件要剪切到哪个地方的全路径
        NSString *fullpath2 = [to stringByAppendingPathComponent:fileName];

        //4.4 执行剪切
        [manager moveItemAtPath:fullPath1 toPath:fullpath2 error:nil];

        NSLog(@"%@---%@---%@",[NSThread currentThread],fullPath1,fullpath2);
    });
}

(6)GDC其他内容

01 使用Crearte函数创建的并发队列和全局并发队列的主要区别:
    1. 全局并发队列在整个应用程序中本身是默认存在的,并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只是选择其中的一个直接拿来用。
    2. Crearte函数是实打实的从头开始去创建一个队列。

02 GCD内存管理
    1. 在iOS6.0之前,在GCD中凡是使用了带Crearte和retain的函数在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。
    2. 在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就OK。

03 在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效(没有给出具体原因)

04 其它区别涉及到XNU内核的系统级线程编程,不一一列举
05 给出一些参考资料(可以自行研究):

GCDAPI:https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_queue_create

Libdispatch版本源码:http://www.opensource.apple.com/source/libdispatch/libdispatch-187.5/

相关文章

  • 多线程之GCD

    GCD介绍 1、GCD简介 2、GCD任务和队列 3、GCD 的基本使用 4、GCD 线程间的通信 5、GCD 的...

  • 扩展GCD(求逆元,解同余方程等等)

    首先要知道gcd函数的基本性质:gcd(a,b)=gcd(b,a)=gcd(|a|,|b|)=gcd(b,a%b)...

  • iOS - GCD

    目录 GCD简介 GCD核心概念 GCD队列的使用 GCD的常见面试题 GCD简介 Grand Central D...

  • iOS-多线程:GCD

    GCD 简介 GCD 任务和队列 GCD 的使用步骤 GCD 的基本使用(6种不同组合区别) GCD 线程间的通信...

  • 浅析GCD

    GCD目录: 1. GCD简介 为什么要用GCD呢? GCD可用于多核的并行运算GCD会自动利用更多的CPU内核(...

  • 7.3 多线程-GCD

    多线程-GCD 多线程-GCD-串行并行 多线程-GCD.png GCD-线程的通讯、延时操作、定时器 GCD-线...

  • iOS 多线程--GCD

    一、GCD基本介绍 1.GCD简介 GCD是Grand Central Dispatch的缩写,GCD是苹果推出的...

  • 自用算法模板(JAVA版)

    一、数论 1)GCD GCD(求最大公约数) QGCD(快速GCD) extGCD(拓展GCD,解决ax + by...

  • GCD介绍

    一、GCD简单介绍 什么是GCD GCD优势 任务和队列 GCD有2个核心概念 GCD的使用就2个步骤 将任务添加...

  • 7.多线程基础(七)GCD加强

    1.GCD串行队列和并发队列 2.GCD延时执行 3.GCD线程组:(的作用) 4.GCD定时器: GCD的实现 ...

网友评论

      本文标题:GCD

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