GCD详细总结

作者: iOS心安 | 来源:发表于2020-12-01 21:58 被阅读0次

一、什么是GCD

二、我们为什么要用GCD技术

三、在实际开发中如何使用GCD更好的实现我们的需求

  1. Synchronous & Asynchronous 同步 & 异步
  2. Serial Queues & Concurrent Queues 串行 & 并发
  3. Global Queues全局队列
  4. Main Queue主队列
  5. 同步的作用

一、什么是GCD
  GCD 是基于 C 的 API,它是 libdispatch 的市场名称,而 libdispatch 作为 Apple 的一个库,为并发代码在多核硬件(跑 iOS 或 OS X )上执行提供有力支持。


二、我们为什么要用GCD技术

  • GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。
  • GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
  • GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。
  • GCD旨在替换NSThread等线程技术
  • GCD可充分利用设备的多核
  • GCD可自动管理线程的生命周期

三、在实际开发中如何使用GCD更好的实现我们的需求

1、Synchronous & Asynchronous 同步 & 异步

同步任务执行方式:在当前线程中执行,必须等待当前语句执行完毕,才会执行下一条语句

#pragma mark
#pragma mark - 同步方法
/**
 同步的打印顺序
 打印 begin
 打印 [NSThread currentThread]
 打印 end
 */
- (void)syncTask {
    
    NSLog(@"begin");
    
    // 1.GCD同步方法
    /**
     参数1:队列 第一个参数0其实为队列优先级DISPATCH_QUEUE_PRIORITY_DEFAULT,如果要适配 iOS 7.0 & 8.0,则始终为0
     参数2:任务
     */
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        // 任务中要执行的代码
        NSLog(@"%@", [NSThread currentThread]);
        
    });
    
    NSLog(@"end");
    
}
同步方法

异步任务执行方式:不在当前线程中执行,不用等待当前语句执行完毕,就可以执行下一条语句

#pragma mark
#pragma mark - 异步方法
/**
 异步的打印顺序
 打印 begin
 打印 一般情况下为end,极少数情况下会很快开辟完新的线程,先打印出[NSThread currentThread]
 */
- (void)asyncTask {
    
    /**
     异步:不会在“当前线程”执行,会首先去开辟新的子线程,开辟线程需要花费时间
     */
    
    NSLog(@"begin");
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"%@", [NSThread currentThread]);
    });
    
    NSLog(@"end");
    
}

异步方法

1. Serial Queues & Concurrent Queues 串行 & 并发

串行队列调度同步和异步任务执行

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

#pragma mark
#pragma mark - 串行队列同步方法
/**
 串行队列,同步方法
 1.打印顺序 : 从上到下,依次打印,因为是串行的
 2.在哪条线程上执行 : 主线程,因为是同步方法,所以在当前线程里面执行,恰好当前线程是主线程,所以它就在主线程上面执行
 
 应用场景:开发中很少用
 */
- (void)serialSync {
    // 1.创建一个串行队列
    /**
     参数1:队列的表示符号,一般是公司的域名倒写
     参数2:队列的类型
         DISPATCH_QUEUE_SERIAL 串行队列
         DISPATCH_QUEUE_CONCURRENT 并发队列
     */
    dispatch_queue_t serialQuene = dispatch_queue_create("com.baidu", DISPATCH_QUEUE_SERIAL);
    
    // 创建任务
    void (^task1) () = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2) () = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3) () = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 添加任务到队列,同步方法执行
    dispatch_sync(serialQuene, task1);
    dispatch_sync(serialQuene, task2);
    dispatch_sync(serialQuene, task3);
}

串行队列同步方法
#pragma mark
#pragma mark - 串行队列异步方法
/**
 串行队列,异步方法
 1.打印顺序:从上到下,依次执行,它是串行队列
 2.在哪条线程上执行:在子线程,因为它是异步执行,异步就是不在当前线程里面执行
 
 应用场景:耗时间,有顺序的任务
    1.登录--->2.付费--->3.才能看
 
 */
- (void)serialAsync {
    // 除了第三步,和串行同步方法中都是一样的
    // 1.创建一个串行队列
    dispatch_queue_t serialQuene = dispatch_queue_create("com.baidu", DISPATCH_QUEUE_SERIAL);
    
    // 2.创建任务
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 3.添加任务到队列
    dispatch_async(serialQuene, task1);
    dispatch_async(serialQuene, task2);
    dispatch_async(serialQuene, task3);
}

串行队列异步方法

并发队列调度异步任务执行
并发队列特点:
  以先进先出的方式,并发调度队列中的任务执行
  如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务
  如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行

#pragma mark
#pragma mark - 并发队列同步任务
/**
 并发队列,同步任务
 1.打印顺序:因为是同步,所以依次执行
 2.在哪条线程上执行:主线程,因为它是同步方法,它就在当前线程里面执行,也就是在主线程里面依次执行
 
 当并发队列遇到同步的时候还是依次执行,所以说方法(同步/异步)的优先级会比队列的优先级高
 
 * 只要是同步方法,都只会在当前线程里面执行,不会开子线程
 
 应用场景:
    开发中几乎不用
 
 */
- (void)serialSync {
    
    /**
     参数1:队列的表示符号,一般是公司的域名倒写
     参数2:队列的类型
     DISPATCH_QUEUE_SERIAL 串行队列
     DISPATCH_QUEUE_CONCURRENT 并发队列
     */
    
    // 1.创建并发队列
    dispatch_queue_t serialSync = dispatch_queue_create("com.xiaojukeji", DISPATCH_QUEUE_CONCURRENT);
    
    // 2.创建任务
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 3.添加任务到并发队列
    dispatch_sync(serialSync, task1);
    dispatch_sync(serialSync, task2);
    dispatch_sync(serialSync, task3);
}

并发队列同步任务
#pragma mark
#pragma mark - 并发队列异步任务
/**
 1.打印顺序:无序的
 2.在哪条线程上执行:在子线程上执行,每一个任务都在它自己的线程上执行
        可以创建N条子线程,它是由底层可调度线程池来决定的,可调度线程池它是有一个重用机制
 
 应用场景
    同时下载多个影片
 */
- (void)serialAsync {
    // 1.创建并发队列
    dispatch_queue_t serialAsync = dispatch_queue_create("com.xiaojukeji", DISPATCH_QUEUE_CONCURRENT);
    
    // 2.创建任务
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 3.将任务添加到并发队列
    dispatch_async(serialAsync, task1);
    dispatch_async(serialAsync, task2);
    dispatch_async(serialAsync, task3);
}

并发队列异步任务

3. 全局队列
全局队列是系统为了方便程序员开发提供的,其工作表现与并发队列一致

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

全局队列:没有名称,无论 MRC & ARC 都不需要考虑释放,日常开发中,建议使用"全局队列";
并发队列:有名字,和 NSThread 的 name 属性作用类似,如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象;
dispatch_barrier 必须使用自定义的并发队列;
开发第三方框架时,建议使用并发队列.

参数:
参数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);

#pragma mark
#pragma mark - 全局队列同步任务
/**
 全局队列,同步任务
 1.打印顺序:依次执行,因为它是同步的
 2.在哪条线程上执行:主线程,因为它是同步方法,它就在当前线程里面执行
 
 当它遇到同步的时候,并发队列还是依次执行,所以说,方法的优先级比队列的优先级高
 
 * 只要是同步方法,都只会在当前线程里面执行,不会开子线程
 
 应用场景:开发中几乎不用
 */
- (void)globalSync {
    /**
     参数1:
        IOS7:表示的优先级
        IOS8:服务质量
        为了保证兼容IOS7&IOS8一般传入0
     
     参数2:未来使用,传入0
     */
    
    NSLog(@"begin");
    // 1.创建全局队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    // 2.创建任务
    void (^task1)() = ^() {
        NSLog(@"task1----%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2----%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3----%@", [NSThread currentThread]);
    };
    
    // 3.添加任务到全局队列
    dispatch_sync(globalQueue, task1);
    dispatch_sync(globalQueue, task2);
    dispatch_sync(globalQueue, task3);

    NSLog(@"end");
}

全局队列同步任务
#pragma mark
#pragma mark - 全局队列异步任务
/**
 全局队列,异步方法
 1.打印顺序:无序的
 2.在子线程上执行,每一个任务都在它自己的线程上执行,线程数由底层可调度线程池来决定的,可调度线程池有一个重用机制
 应用场景:
    蜻蜓FM同时下载多个声音
 */
- (void)globalAsync {
    
    NSLog(@"begin");
    
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    dispatch_async(globalQueue, task1);
    dispatch_async(globalQueue, task2);
    dispatch_async(globalQueue, task3);
    
    NSLog(@"end");
    
}

全局队列异步任务

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

队列获取
  主队列是负责在主线程调度任务的
  会随着程序启动一起创建
  主队列只需要获取不用创建

#pragma mark
#pragma mark - 主队列异步任务
/**
 主队列,异步任务
 1.执行顺序:依次执行,因为它在主线程里面执行
 * 似乎与我们的异步任务有所冲突,但是因为它是主队列,所以,只在主线程里面执行
 
 2.是否会开线程:不会,因为它在主线程里面执行
 
 应用场景:
    当做了耗时操作之后,我们需要回到主线程更新UI的时候,就非它不可
 */
- (void)mainAsync {
    
    NSLog(@"begin");
    
    // 1.创建主队列
    dispatch_queue_t mainAsync = dispatch_get_main_queue();
    
    // 2.创建任务
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };

    dispatch_async(mainAsync, task1);
    dispatch_async(mainAsync, task2);
    dispatch_async(mainAsync, task3);
    
    NSLog(@"end");
    
}

主队列异步任务
#pragma mark
#pragma mark - 主队列同步方法有问题,不能用是个奇葩,会造成死锁
/**
 主队列,同步任务有问题,不能用,彼此都在等对方是否执行完了,所以是互相死等
 主队列只有在主线程空闲的时候,才会去调度它里面的任务去执行
 */
- (void)mainSync {
    
    NSLog(@"begin");
    // 1.创建主队列
    dispatch_queue_t mainSync = dispatch_get_main_queue();
    
    // 2.创建任务
    void (^task1)() = ^() {
        NSLog(@"task1---%@", [NSThread currentThread]);
    };
    
    void (^task2)() = ^() {
        NSLog(@"task2---%@", [NSThread currentThread]);
    };
    
    void (^task3)() = ^() {
        NSLog(@"task3---%@", [NSThread currentThread]);
    };
    
    // 3.添加任务到主队列中
    dispatch_sync(mainSync, task1);
    dispatch_sync(mainSync, task2);
    dispatch_sync(mainSync, task3);
    
    NSLog(@"end");
}

主队列同步方法有问题,不能用是个奇葩,会造成死锁

Deadlock 死锁
  两个(有时更多)东西——在大多数情况下,是线程——所谓的死锁是指它们都卡住了,并等待对方完成或执行其它操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。

5. 同步的作用
同步任务,可以让其他异步执行的任务,依赖某一个同步任务,例如:在用户登录之后,才允许异步下载文件!

#pragma mark
#pragma mark - 模拟登录下载多个电影数据
/**
 同步的作用:保证我们任务执行的先后顺序
 1.登录
 
 2.同时下载三部电影
 */
- (void)loadManyMovie {
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSLog(@"%@", [NSThread currentThread]);
        // 1.登录,同步在当前线程里面工作
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            
            NSLog(@"登录了---%@", [NSThread currentThread]);
            sleep(3);
            
        });
        
        // 2.同时下载三部电影()
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"正在下载第一个电影---%@", [NSThread currentThread]);
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"正在下载第二个电影---%@", [NSThread currentThread]);
        });
        
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"正在下载第三个电影---%@", [NSThread currentThread]);
        });
        
        dispatch_sync(dispatch_get_main_queue(), ^{
            [NSThread sleepForTimeInterval:1.0];
            NSLog(@"计算机将在三秒后关闭---%@", [NSThread currentThread]);
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"关机了---%@", [NSThread currentThread]);
            });
        });
        
    });
    
}

模拟登录下载多个电影数据

本文来自:淡泊宁静_JP

上一篇:实现多线程的方法有哪些?
下一篇:iOS多线程之NSThread的使用

相关文章

网友评论

    本文标题:GCD详细总结

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