iOS-底层原理 26:GCD 之 函数与队列

2020-11-04  本文已影响0人  Style_月月

iOS 底层原理 文章汇总

本文的主要目的是理解不同队列与不同函数之间组合的情况

GCD简介

GCD优势

【重点】用一句话总结GCD就是:将任务添加到队列,并指定任务执行的函数

GCD核心

在日常开发中,GCD一般写成下面这种形式

 dispatch_async( dispatch_queue_create("com.CJL.Queue", NULL), ^{
   NSLog(@"GCD基本使用");
});

将上述代码拆分,方便我们来理解GCD核心 主要是由 任务 + 队列 + 函数 构成

//********GCD基础写法********
//创建任务
dispatch_block_t block = ^{
    NSLog(@"hello GCD");
};

//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL);

//将任务添加到队列,并指定函数执行
dispatch_async(queue, block);

注意

这里的任务是指执行操作的意思,在使用dispatch_block_t创建任务时,主要有以下两点说明

函数与队列

函数

在GCD中执行任务的方式有两种,同步执行和异步执行,分别对应 同步函数dispatch_sync异步函数dispatch_async,两者对比如下

所以,综上所述,两种执行方式的主要区别有两点:

队列

串行队列 和 并发队列

多线程中所说的队列(Dispatch Queue)是指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,遵循先进先出(FIFO)原则,即新任务总是被插入到队尾,而任务的读取从队首开始读取。每读取一个任务,则动队列中释放一个任务,如下图所示

队列图示

在GCD中,队列主要分为串行队列(Serial Dispatch Queue)并发队列(Concurrent Dispatch Queue)两种,如下图所示

串行 and 并行
// 串行队列的获取方法
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.CJL.Queue", NULL);
    dispatch_queue_t serialQueue2 = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_SERIAL);
// 并发队列的获取方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);

主队列 和 全局并发队列

在GCD中,针对这两种队列,分别提供了主队列(Main Dispatch Queue)全局并发队列(Global Dispatch Queue)

//主队列的获取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并发队列的获取方法
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

//优先级从高到低(对应的服务质量)依次为
- DISPATCH_QUEUE_PRIORITY_HIGH       -- QOS_CLASS_USER_INITIATED
- DISPATCH_QUEUE_PRIORITY_DEFAULT    -- QOS_CLASS_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW        -- QOS_CLASS_UTILITY
- DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND

全局并发队列 + 主队列 配合使用

在日常开发中,全局队列+并发并列一般是这样配合使用的

//主队列 + 全局并发队列的日常使用
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //执行耗时操作
        dispatch_async(dispatch_get_main_queue(), ^{
            //回到主线程进行UI操作
        });
    });

函数与队列的不同组合

串行队列 + 同步函数

【任务按顺序执行】:任务一个接一个的在当前线程执行,不会开辟新线程

串行队列 + 同步函数

串行队列 + 异步函数

【任务按顺序执行】:任务一个接一个的执行,会开辟新线程

串行队列 + 异步函数

并发队列 + 同步函数

【任务按顺序执行】:任务一个接一个的执行,不开辟线程

并发队列 + 同步函数

并发队列 + 异步函数

【任务乱序执行】:任务执行无顺序,会开辟新线程

并发队列 + 异步函数

主队列 + 同步函数

【造成死锁】:任务相互等待,造成死锁

主队列 + 同步函数

造成死锁的原因分析如下:

死锁现象

主队列 + 异步函数

【任务按顺序执行】:任务一个接一个的执行,不开辟线程

主队列 + 异步函数

全局并发队列 + 同步函数

【任务按顺序执行】:任务一个接一个的执行,不开辟新线程

全局并发队列 + 同步函数

全局并发队列 + 异步函数

【任务乱序执行】:任务乱序执行,会开辟新线程

全局并发队列 + 异步函数

总结

函数\队列 串行队列 并发队列 主队列 全局并发队列
同步函数 顺序执行,不开辟线程 顺序执行,不开辟线程 死锁 顺序执行,不开辟线程
异步函数 顺序执行,开辟线程 乱序执行,开辟线程 顺序执行,不开辟线程 乱序执行,开辟线程

相关面试题解析

【面试题 - 1】异步函数+并行队列

下面代码的输出顺序是什么?

- (void)interview01{
    //并行队列
    dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 耗时
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
----------打印结果-----------
输出顺序为:1 5 2 4 3

代码分析

如下图所示,红线表示任务的执行顺序


分析图示

代码修改

【面试题 - 2】异步函数嵌套同步函数 + 并发队列

下面代码的输出顺序是什么?

- (void)interview02{
    //并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    //异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        //同步函数
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

----------打印结果-----------
输出顺序为:1 5 2 3 4

分析

打印结果

【面试题 - 3】异步函数嵌套同步函数 + 串行队列(即同步队列)

下面代码的执行顺序是什么?会出现什么情况?为什么?

- (void)interview03{
    // 同步队列
    dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", NULL);
    NSLog(@"1");
    // 异步函数
    dispatch_async(queue, ^{
        NSLog(@"2");
        // 同步函数
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}

----------打印结果-----------
输出顺序为:1 5 2 死锁崩溃

分析

如下图所示,红色表示任务执行顺序,黑色虚线表示等待


分析图示

修改

去掉任务4,执行顺序是什么?

【面试题 - 4 - 新浪】 异步函数 + 同步函数 + 并发队列

下面代码的执行顺序是什么?(答案是 AC)
A: 1230789
B: 1237890
C: 3120798
D: 2137890

- (void)interview04{
    //并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{ // 耗时
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    
    // 同步
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    
    NSLog(@"0");

    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });
}

----------打印结果-----------
输出顺序为:(1 2 3 无序)0(7 8 9 无序),可以确定的是 0 一定在3之后,在789之前

分析

以下是不同的执行顺序的打印


不同的执行顺序

【面试题 - 5 - 美团】下面代码中,队列的类型有几种?

//串行队列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.CJL.Queue", NULL);
    
//并发队列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
    
//主队列 - Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
//全局并发队列 - Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

队列总共有两种: 并发队列串行队列

上一篇下一篇

猜你喜欢

热点阅读