GCD

  1. 概念
  2. GCD队列
  3. 使用总结
  4. 实用api
  5. 实用遇到的坑

一、 概念

GCD(grand central dispatch),称为中央调度 ,在Mac OS X10.6 和iOS4以及以上的系统引进的多线程解决技术方案,将任务(要在线程中执行的操作block)添加到队列(自己创建或使用全局并发队列),并且指定执行任务的方式(异步dispatch_async,同步dispatch_sync)。从基本功能看GCD与 NSOperationQueue 有点相似,都是将任务切分为多个单一任务放进工作队列并发性地或者串行地执行。

apple 开发者文档这样介绍: GCD的诞生原因,它的工作方式以及解决的问题;

Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in macOS, iOS, watchOS, a***OS.
翻译: GCD 由独特的语言特性、运行时库和有系统提供的进系统组成,全面提高了在MacOS 、iOS、watch OS 、tv OS硬件上代码的同步执行。

The BSD subsystem, Core Foundation, and Cocoa APIs have all been extended to use these enhancements to help both the system and your application to run faster, more efficiently, and with improved responsiveness. Consider how difficult it is for a single application to use multiple cores effectively, let alone doing it on different computers with different numbers of computing cores or in an environment with multiple applications competing for those cores. GCD, operating at the system level, can better accommodate the needs of all running applications, matching them to the available system resources in a balanced fashion.
翻译:BSD子系统层(这里指的是操作系统内核的外部系统) 、 Core Foundation层、Cocoa APIs 层已经使用GCD技术去帮助系统和APP运行的更快,效率更高,并且得到了激烈反应去改进。考虑到单个应用使用多核效果是困难的,更不用说让我们在带着多个处理器的计算机上使用这项技术或者一个多核环境运行多个应用。GCD,基于系统级别去操作,可以更好的适应所有应用的需求,更合理均衡的让它们找到可用的系统资源。

从上图得出GCD诞生的作用是解决单应用不能充分利用多核处理高效利用系统资源的问题推出,基于系统BCD内核调度层进行时间调度,更加高效执行多任务。

二、GCD的核心是队列调度,它主要包含以下的队列:

GCD分为 串行队列和并发队列,都是采用FIFO方式进行管理任务的输入和输出
串行队列: 按次序依次执行(不管执行方式 dispatch_async 还是 dispatch_sync) DISPATCH_QUEUE_SERIAL
并发队列:CPU在一段时间内按照时间片进行快速切换线程执行,达到同一时间段多个线程同时执行(只有在dispatch_async才可以; DISPATCH_QUEUE_CONCURRENT
主队列: UI队列 dispatch_queue_main
组队列: 一组队列的集合 dispatch_queue_global

三、GCD的使用

了解下 同步/异步 串行/并发概念

同步:

用dispatch_sync,这个函数会用户的block任务加入指定的队列中,知道block执行完毕,dispatch_sync 才会返回,因此在block没有执行完毕之前,调用dispatch_sync方法的线程都是阻塞的;

异步:

用dispatch_async,这个函数会用户的block任务加入指定的队列中,和同步不同的是当把block任务加入到指定的队列后dispatch_async就会立马返回啦,所以在block没有执行之前调用dispatch_async是不存在阻塞的;

串行队列:

队列的任务是按照先来后到的顺序执行。不仅如此,还可以保证在执行某个任务时,在它前面进入队列的所有任务肯定执行完了。对于每一个不同的串行队列,系统会为这个队列建立唯一的线程来执行代码。

并发队列

跟串行队列一个共同点是队列里的任务都是先进先出,但是在并发队列里所有的任务开始都是按进入队列的顺序依次执行,但是每个任务的执行耗时不同,所有执行完的顺序也不一样 。

使用总结

  1. 主线程的时间优先
  2. 串行同步执行: 执行完一个任务,再执行下一个任务。不开启新线程,都在主线程执行。
  3. 串行异步执行: 开启新线程,但因为任务是串行的,所以还是按顺序执行任务。
  4. 并发队列异步执行: 并发执行,会开启新的线程。
  5. 并发队列同步执行; 按顺序执行,不会开启新的线程。
  6. 主队列同步事件:
  7. 主队列异步事件: 按次序执行。

三、GCD其他实用技术

线程间通信 当需要回到主线程刷新UI

 // 异步
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 耗时操作放在这里,例如下载图片。(运用线程休眠两秒来模拟耗时操作)
        [NSThread sleepForTimeInterval:2];
        NSString *picURLStr = @"http://www.bangmangxuan.net/uploads/allimg/160320/74-160320130500.jpg";
        NSURL *picURL = [NSURL URLWithString:picURLStr];
        NSData *picData = [NSData dataWithContentsOfURL:picURL];
        UIImage *image = [UIImage imageWithData:picData];
        
        // 回到主线程处理UI
        dispatch_async(dispatch_get_main_queue(), ^{
            // 在主线程上添加图片
            self.imageView.image = image;
        });
    });

GCD栅栏 : 当需要异步执行多个任务但是想一批执行完再实行其他的

// 并发队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    // 异步执行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"栅栏:并发异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"栅栏:并发异步2   %@",[NSThread currentThread]);
        }
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"------------barrier------------%@", [NSThread currentThread]);
        NSLog(@"******* 并发异步执行,但是34一定在12后面 *********");
    });
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"栅栏:并发异步3   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"栅栏:并发异步4   %@",[NSThread currentThread]);
        }
    });
}

GCD延时执行

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 5秒后异步执行
    NSLog(@"我已经等待了5秒!");
});

执行一次

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    NSLog(@"程序运行过程中我只执行了一次!");
});

GCD的坑

主队列同步事件死锁问题

```
  • (void) viewDidLoad{
    dispatch_queue_t mainQueue = [self getMainQueue];
    NSLog(@"任务1");
    dispatch_sync(mainQueue, ^{
    NSLog(@"任务2");
    });
    NSLog(@"任务3");

}
```

原因: viewDidLoad函数是主队列的任务并且位于主队列的头部,此时将一个block加入主队列,由于同步机制, block必须等到viewDidLoad函数执行完毕才能执行,但是 viewDidLoad函数里任务dispatch_sync也得必须等到block完全执行完毕,它才能完全返回出去,因为viewDidLoad函数等待block,block等待viewDidLoad函数 由此造成死锁。由此可以看出是队列引起的死锁。
如下图:


image.png

解决GCD死锁的方法分析:

通过上面的分析造成死锁的原因是 等待闭环引起,我们只要改变一个节点即可,解决方案如下:

  1. 采用异步方式进行,这样的dispatch_sync不必等待block执行完毕即可返回出去,程序继续执行下面的逻辑;
  2. 将block放在其他队列即可

performSelector与GCD对比

performSelector在ARC情况下会出现内存泄漏的情况、GCD自身存在内存管理很好的避免了内存泄漏。