GCD

(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)