OC底层原理 - 20 GCD之应用篇

GCD

简介

  • GCD全称是Grand Central Dispatch,由C语言开发的。
  • GCD是苹果公司为多核的并行运算提出的解决方案
  • GCD会自动利用更多的CPU内核(比如双核、四核)。
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。因此在使用GCD的时候,只需要管理执行的任务,而不需要管理线程。

GCD核心

GCD的核心分为三步骤:

  1. 创建任务
  2. 创建队列
  3. 将任务添加到队列中,并指定执行任务的函数

根据核心三步骤,写一个简单的GCD代码

//1. 创建任务
dispatch_block_t block = ^{
    NSLog(@"hello GCD");
};
//2. 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.HQ.Queue", DISPATCH_QUEUE_SERIAL);

//3. 将任务添加到队列中,并指定执行方式,此处为异步执行
dispatch_async(queue, block);

//上面三个步骤的代码可以合并成以下形式
dispatch_async(dispatch_queue_create("com.HQ.Queue", DISPATCH_QUEUE_SERIAL), {
    NSLog(@"hello GCD");
});

注意:

  • 任务需要使用block进行封装
  • 任务的block没有参数,也没有返回值

函数与队列

函数

在GCD中,执行任务的方式有两种:同步执行和异步执行。

  • 同步执行

    • 使用函数dispatch_sync
    • 必须等待当前语句执行完毕,才会执行下一条语句
    • 不会开启线程,即不具备开启新线程的能力
    • 由于不会开启新线程,因此block任务是在当前线程中执行
  • 异步执行

    • 使用函数dispatch_async
    • 不用等待当前语句执行完毕,就可以执行下一条语句
    • 会开启线程执行block任务,即具备开启新线程的能力(注意:不表示每一次异步执行都会开新线程)

队列

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

队列.png

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

image.png

  • 串行队列
    • 每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个

    • 创建串行队列的方式如下:

      dispatch_queue_t serialQueue1 = dispatch_queue_create("com.HQ.Queue", NULL);
      dispatch_queue_t serialQueue2 = dispatch_queue_create("com.HQ.Queue", DISPATCH_QUEUE_SERIAL);
      
  • 并发队列
    • 一次可以并发执行多个任务,即开启多个线程,并同时执行任务

    • 创建并发队列的方式如下:

      dispatch_queue_t concurrentQueue = dispatch_queue_create("com.HQ.Queue", DISPATCH_QUEUE_CONCURRENT);
      

在GCD中,提供了两种特殊的队列:主队列(Main Dispatch Queue)全局并发队列(Global Dispatch Queue)

  • 主队列

    • 主线程上用于调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前自动创建,通常UI操作必须使用主队列
    • 不会开启线程
    • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
    • 使用dispatch_get_main_queue()获得主队列
  • 全局并发队列

    • GCD提供的默认的并发队列,使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局并发队列
    • 使用dispatch_get_global_queue函数获取全局并发队列
    //全局并发队列的获取方法
    //参数1:队列优先级
    //参数2:保留参数
    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
    

函数与队列的不同组合

  • 串行队列 + 同步函数:任务是一个接着一个完成,不会开辟新的线程
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"[任务开始] -- %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.HQ.Queue", DISPATCH_QUEUE_SERIAL);
    for(int i = 0; i<5; i++){
        dispatch_sync(queue, ^{
            NSLog(@"串行队列 + 同步执行 + %d", i);
            NSLog(@"%@", [NSThread currentThread]);
        });
    }
    NSLog(@"[任务结束] -- %@", [NSThread currentThread]);
}

//执行结果如下
2021-04-02 14:30:09.694649+0800 001---函数与队列[79317:12540708] [任务开始] -- <NSThread: 0x600003bdc6c0>{number = 1, name = main}
2021-04-02 14:30:09.694932+0800 001---函数与队列[79317:12540708] 串行队列 + 同步执行 + 0
2021-04-02 14:30:09.695138+0800 001---函数与队列[79317:12540708] <NSThread: 0x600003bdc6c0>{number = 1, name = main}
2021-04-02 14:30:09.695302+0800 001---函数与队列[79317:12540708] 串行队列 + 同步执行 + 1
2021-04-02 14:30:09.695468+0800 001---函数与队列[79317:12540708] <NSThread: 0x600003bdc6c0>{number = 1, name = main}
2021-04-02 14:30:09.695619+0800 001---函数与队列[79317:12540708] 串行队列 + 同步执行 + 2
2021-04-02 14:30:09.695794+0800 001---函数与队列[79317:12540708] <NSThread: 0x600003bdc6c0>{number = 1, name = main}
2021-04-02 14:30:09.695957+0800 001---函数与队列[79317:12540708] 串行队列 + 同步执行 + 3
2021-04-02 14:30:09.696411+0800 001---函数与队列[79317:12540708] <NSThread: 0x600003bdc6c0>{number = 1, name = main}
2021-04-02 14:30:09.696766+0800 001---函数与队列[79317:12540708] 串行队列 + 同步执行 + 4
2021-04-02 14:30:09.697152+0800 001---函数与队列[79317:12540708] <NSThread: 0x600003bdc6c0>{number = 1, name = main}
2021-04-02 14:30:09.697933+0800 001---函数与队列[79317:12540708] [任务结束] -- <NSThread: 0x600003bdc6c0>{number = 1, name = main}
  • 串行队列 + 异步函数:任务是一个接着一个完成,会开辟新的线程
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"[任务开始] -- %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.HQ.Queue", DISPATCH_QUEUE_SERIAL);
    for(int i = 0; i<5; i++){
        dispatch_async(queue, ^{
            NSLog(@"串行队列 + 异步执行 + %d", i);
            NSLog(@"%@", [NSThread currentThread]);
        });
    }
    NSLog(@"[任务结束] -- %@", [NSThread currentThread]);
}

//执行结果如下
2021-04-02 14:58:21.928131+0800 001---函数与队列[79640:12562323] [任务开始] -- <NSThread: 0x600001edc400>{number = 1, name = main}
2021-04-02 14:58:21.928465+0800 001---函数与队列[79640:12562423] 串行队列 + 异步执行 + 0
2021-04-02 14:58:21.928495+0800 001---函数与队列[79640:12562323] [任务结束] -- <NSThread: 0x600001edc400>{number = 1, name = main}
2021-04-02 14:58:21.928672+0800 001---函数与队列[79640:12562423] <NSThread: 0x600001eef5c0>{number = 6, name = (null)}
2021-04-02 14:58:21.928861+0800 001---函数与队列[79640:12562423] 串行队列 + 异步执行 + 1
2021-04-02 14:58:21.929018+0800 001---函数与队列[79640:12562423] <NSThread: 0x600001eef5c0>{number = 6, name = (null)}
2021-04-02 14:58:21.929183+0800 001---函数与队列[79640:12562423] 串行队列 + 异步执行 + 2
2021-04-02 14:58:21.929364+0800 001---函数与队列[79640:12562423] <NSThread: 0x600001eef5c0>{number = 6, name = (null)}
2021-04-02 14:58:21.929862+0800 001---函数与队列[79640:12562423] 串行队列 + 异步执行 + 3
2021-04-02 14:58:21.930451+0800 001---函数与队列[79640:12562423] <NSThread: 0x600001eef5c0>{number = 6, name = (null)}
2021-04-02 14:58:21.930978+0800 001---函数与队列[79640:12562423] 串行队列 + 异步执行 + 4
2021-04-02 14:58:21.931508+0800 001---函数与队列[79640:12562423] <NSThread: 0x600001eef5c0>{number = 6, name = (null)}
  • 并发队列 + 同步函数:任务是一个接着一个完成,不会开辟新的线程。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"[任务开始] -- %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.HQ.Queue", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i<5; i++){
        dispatch_sync(queue, ^{
            NSLog(@"并发队列 + 同步执行 + %d", i);
            NSLog(@"%@", [NSThread currentThread]);
        });
    }
    NSLog(@"[任务结束] -- %@", [NSThread currentThread]);
}

//执行结果如下
2021-04-02 15:02:05.764874+0800 001---函数与队列[79685:12565854] [任务开始] -- <NSThread: 0x600000c3c5c0>{number = 1, name = main}
2021-04-02 15:02:05.765200+0800 001---函数与队列[79685:12565854] 并发队列 + 同步执行 + 0
2021-04-02 15:02:05.765423+0800 001---函数与队列[79685:12565854] <NSThread: 0x600000c3c5c0>{number = 1, name = main}
2021-04-02 15:02:05.765595+0800 001---函数与队列[79685:12565854] 并发队列 + 同步执行 + 1
2021-04-02 15:02:05.765773+0800 001---函数与队列[79685:12565854] <NSThread: 0x600000c3c5c0>{number = 1, name = main}
2021-04-02 15:02:05.765933+0800 001---函数与队列[79685:12565854] 并发队列 + 同步执行 + 2
2021-04-02 15:02:05.766366+0800 001---函数与队列[79685:12565854] <NSThread: 0x600000c3c5c0>{number = 1, name = main}
2021-04-02 15:02:05.766516+0800 001---函数与队列[79685:12565854] 并发队列 + 同步执行 + 3
2021-04-02 15:02:05.766727+0800 001---函数与队列[79685:12565854] <NSThread: 0x600000c3c5c0>{number = 1, name = main}
2021-04-02 15:02:05.767189+0800 001---函数与队列[79685:12565854] 并发队列 + 同步执行 + 4
2021-04-02 15:02:05.767685+0800 001---函数与队列[79685:12565854] <NSThread: 0x600000c3c5c0>{number = 1, name = main}
2021-04-02 15:02:05.768114+0800 001---函数与队列[79685:12565854] [任务结束] -- <NSThread: 0x600000c3c5c0>{number = 1, name = main}
  • 并发队列 + 异步函数:任务执行没有顺序,会开辟新的线程
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"[任务开始] -- %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_queue_create("com.HQ.Queue", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i<5; i++){
        dispatch_async(queue, ^{
            NSLog(@"并发队列 + 异步执行 + %d", i);
            NSLog(@"%@", [NSThread currentThread]);
        });
    }
    NSLog(@"[任务结束] -- %@", [NSThread currentThread]);
}

//执行结果如下
2021-04-02 15:04:09.229740+0800 001---函数与队列[79720:12568189] [任务开始] -- <NSThread: 0x600001254a80>{number = 1, name = main}
2021-04-02 15:04:09.230070+0800 001---函数与队列[79720:12568287] 并发队列 + 异步执行 + 1
2021-04-02 15:04:09.230078+0800 001---函数与队列[79720:12568293] 并发队列 + 异步执行 + 0
2021-04-02 15:04:09.230078+0800 001---函数与队列[79720:12568291] 并发队列 + 异步执行 + 2
2021-04-02 15:04:09.230091+0800 001---函数与队列[79720:12568189] [任务结束] -- <NSThread: 0x600001254a80>{number = 1, name = main}
2021-04-02 15:04:09.230113+0800 001---函数与队列[79720:12568286] 并发队列 + 异步执行 + 3
2021-04-02 15:04:09.230340+0800 001---函数与队列[79720:12568287] <NSThread: 0x600001214880>{number = 5, name = (null)}
2021-04-02 15:04:09.230347+0800 001---函数与队列[79720:12568293] <NSThread: 0x600001254640>{number = 6, name = (null)}
2021-04-02 15:04:09.230363+0800 001---函数与队列[79720:12568291] <NSThread: 0x6000012189c0>{number = 4, name = (null)}
2021-04-02 15:04:09.230494+0800 001---函数与队列[79720:12568286] <NSThread: 0x600001216000>{number = 7, name = (null)}
2021-04-02 15:04:09.230574+0800 001---函数与队列[79720:12568287] 并发队列 + 异步执行 + 4
2021-04-02 15:04:09.233691+0800 001---函数与队列[79720:12568287] <NSThread: 0x600001214880>{number = 5, name = (null)}
  • 主队列 + 同步函数:形成死锁
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"[任务开始] -- %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    for(int i = 0; i<5; i++){
        dispatch_sync(queue, ^{
            NSLog(@"主队列 + 同步执行 + %d", i);
            NSLog(@"%@", [NSThread currentThread]);
        });
    }
    NSLog(@"[任务结束] -- %@", [NSThread currentThread]);
}

总结:主队列+同步函数时,会造成死锁。造成死锁原因如下:

  1. 主队列为串行队列,队列的特性为先进先出,执行完前面的任务才会开始执行后面的任务。
  2. 同步执行的特点是,必须等待当前语句执行完毕,才会执行下一条语句。
  3. viewDidLoad函数是主队列中正在执行的任务

因此,当新的任务block添加至主队列中时,block任务在队列中位于viewDidLoad函数后面,因此需要viewDidLoad函数执行完成之后才将开始执行block任务。而同步执行的特点要求,必须执行完block的内容后再继续下一条语句。这样就造成了相互等待的情况,即死锁。

  • 主队列 + 异步函数:按照主队列中任务顺序执行,不会开辟新线程。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"[任务开始] -- %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    for(int i = 0; i<5; i++){
        dispatch_async(queue, ^{
            NSLog(@"主队列 + 异步执行 + %d", i);
            NSLog(@"%@", [NSThread currentThread]);
        });
    }
    NSLog(@"[任务结束] -- %@", [NSThread currentThread]);
    
//执行结果如下
2021-04-02 15:08:38.209535+0800 001---函数与队列[79797:12573195] [任务开始] -- <NSThread: 0x600003610240>{number = 1, name = main}
2021-04-02 15:08:38.210107+0800 001---函数与队列[79797:12573195] [任务结束] -- <NSThread: 0x600003610240>{number = 1, name = main}
2021-04-02 15:08:38.261887+0800 001---函数与队列[79797:12573195] 主队列 + 异步执行 + 0
2021-04-02 15:08:38.263527+0800 001---函数与队列[79797:12573195] <NSThread: 0x600003610240>{number = 1, name = main}
2021-04-02 15:08:38.264620+0800 001---函数与队列[79797:12573195] 主队列 + 异步执行 + 1
2021-04-02 15:08:38.267018+0800 001---函数与队列[79797:12573195] <NSThread: 0x600003610240>{number = 1, name = main}
2021-04-02 15:08:38.268366+0800 001---函数与队列[79797:12573195] 主队列 + 异步执行 + 2
2021-04-02 15:08:38.270540+0800 001---函数与队列[79797:12573195] <NSThread: 0x600003610240>{number = 1, name = main}
2021-04-02 15:08:38.271863+0800 001---函数与队列[79797:12573195] 主队列 + 异步执行 + 3
2021-04-02 15:08:38.274142+0800 001---函数与队列[79797:12573195] <NSThread: 0x600003610240>{number = 1, name = main}
2021-04-02 15:08:38.275433+0800 001---函数与队列[79797:12573195] 主队列 + 异步执行 + 4
2021-04-02 15:08:38.279348+0800 001---函数与队列[79797:12573195] <NSThread: 0x600003610240>{number = 1, name = main}
  • 全局队列 + 同步函数:与并发队列+同步函数的情况相同,任务是一个接着一个完成,不会开辟新的线程。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"[任务开始] -- %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for(int i = 0; i<5; i++){
        dispatch_sync(queue, ^{
            NSLog(@"全局队列 + 同步执行 + %d", i);
            NSLog(@"%@", [NSThread currentThread]);
        });
    }
    NSLog(@"[任务结束] -- %@", [NSThread currentThread]);
}

//执行结果如下
2021-04-02 15:14:09.630073+0800 001---函数与队列[79868:12578208] [任务开始] -- <NSThread: 0x6000023e4440>{number = 1, name = main}
2021-04-02 15:14:09.630321+0800 001---函数与队列[79868:12578208] 全局队列 + 同步执行 + 0
2021-04-02 15:14:09.630523+0800 001---函数与队列[79868:12578208] <NSThread: 0x6000023e4440>{number = 1, name = main}
2021-04-02 15:14:09.630735+0800 001---函数与队列[79868:12578208] 全局队列 + 同步执行 + 1
2021-04-02 15:14:09.630912+0800 001---函数与队列[79868:12578208] <NSThread: 0x6000023e4440>{number = 1, name = main}
2021-04-02 15:14:09.631072+0800 001---函数与队列[79868:12578208] 全局队列 + 同步执行 + 2
2021-04-02 15:14:09.631251+0800 001---函数与队列[79868:12578208] <NSThread: 0x6000023e4440>{number = 1, name = main}
2021-04-02 15:14:09.631396+0800 001---函数与队列[79868:12578208] 全局队列 + 同步执行 + 3
2021-04-02 15:14:09.631567+0800 001---函数与队列[79868:12578208] <NSThread: 0x6000023e4440>{number = 1, name = main}
2021-04-02 15:14:09.631715+0800 001---函数与队列[79868:12578208] 全局队列 + 同步执行 + 4
2021-04-02 15:14:09.632059+0800 001---函数与队列[79868:12578208] <NSThread: 0x6000023e4440>{number = 1, name = main}
2021-04-02 15:14:09.632473+0800 001---函数与队列[79868:12578208] [任务结束] -- <NSThread: 0x6000023e4440>{number = 1, name = main}
  • 全局队列 + 异步函数:与并发队列+异步函数的情况相同,任务执行没有顺序,会开辟新的线程。
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"[任务开始] -- %@", [NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    for(int i = 0; i<5; i++){
        dispatch_async(queue, ^{
            NSLog(@"全局队列 + 异步执行 + %d", i);
            NSLog(@"%@", [NSThread currentThread]);
        });
    }
    NSLog(@"[任务结束] -- %@", [NSThread currentThread]);
}

//执行结果如下
2021-04-02 15:17:26.686035+0800 001---函数与队列[79911:12581270] [任务开始] -- <NSThread: 0x6000016bc600>{number = 1, name = main}
2021-04-02 15:17:26.686456+0800 001---函数与队列[79911:12581270] [任务结束] -- <NSThread: 0x6000016bc600>{number = 1, name = main}
2021-04-02 15:17:26.686429+0800 001---函数与队列[79911:12581363] 全局队列 + 异步执行 + 1
2021-04-02 15:17:26.686475+0800 001---函数与队列[79911:12581362] 全局队列 + 异步执行 + 2
2021-04-02 15:17:26.686511+0800 001---函数与队列[79911:12581367] 全局队列 + 异步执行 + 0
2021-04-02 15:17:26.686623+0800 001---函数与队列[79911:12581360] 全局队列 + 异步执行 + 3
2021-04-02 15:17:26.686779+0800 001---函数与队列[79911:12581363] <NSThread: 0x600001683040>{number = 6, name = (null)}
2021-04-02 15:17:26.687020+0800 001---函数与队列[79911:12581362] <NSThread: 0x6000016a5bc0>{number = 4, name = (null)}
2021-04-02 15:17:26.687293+0800 001---函数与队列[79911:12581366] 全局队列 + 异步执行 + 4
2021-04-02 15:17:26.687970+0800 001---函数与队列[79911:12581367] <NSThread: 0x6000016f8a00>{number = 7, name = (null)}
2021-04-02 15:17:26.688492+0800 001---函数与队列[79911:12581360] <NSThread: 0x6000016a5e80>{number = 5, name = (null)}
2021-04-02 15:17:26.691704+0800 001---函数与队列[79911:12581366] <NSThread: 0x6000016b2840>{number = 3, name = (null)}

总结

函数\队列 串行队列 并发队列 主队列 全局队列
同步执行 1. 顺序执行;
2.不会开辟新线程
1. 顺序执行;
2.不会开辟新线程
死锁 1. 顺序执行;
2.不会开辟新线程
异步执行 1. 顺序执行;
2.会开辟新线程
1. 乱序执行;
2.会开辟新线程
1. 顺序执行;
2.不会开辟新线程
1. 乱序执行;
2.会开辟新线程

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

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

- (void)interview01{
    //并行队列
    dispatch_queue_t queue = dispatch_queue_create("com.HQ.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

分析:

  • 主线程的任务队列为:任务1、异步block1、任务5,其中异步block1会比较耗费性能,任务1和任务5的任务复杂度是相同的,所以任务1和任务5优先于异步block1执行
  • 在异步block1中,任务队列为:任务2、异步block2、任务4,其中block2相对比较耗费性能,任务2和任务4是复杂度一样,所以任务2和任务4优先于block2执行
  • 最后执行block2中的任务3
  • 在极端情况下,可能出现 任务2先于任务1和任务5执行,原因是出现了当前主线程卡顿或者 延迟的情况

对代码进行修改,将并行队列改成串行队列,其结果是什么?
答:因为代码里面都是异步执行,所以结果仍然是“1 5 2 4 3”

继续修改代码,在任务5之前,休眠2s,即sleep(2),结果是什么?
答:结果可能变成“1 2 4 3 5”

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

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

- (void)interview02{
    //并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.HQ.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

分析:

  • 主线程的任务队列为:任务1、异步block1、任务5,其中异步block1会比较耗费性能,任务1和任务5的任务复杂度是相同的,所以任务1和任务5优先于异步block1执行
  • 在异步block1中,任务队列为:任务2、同步block2、任务4,由于block2为同步执行,因此执行顺序为任务2 -> 同步block2 -> 任务4

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

下面代码的执行顺序是什么?

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

//输出结果:
1 5 2 死锁

分析:

  • 主线程的任务队列为:任务1、异步block1、任务5,其中异步block1会比较耗费性能,任务1和任务5的任务复杂度是相同的,所以任务1和任务5优先于异步block1执行
  • 在异步block1中,任务队列为:任务2、同步block2、任务4。由于block1是同步队列,同步队列的执行顺序为:任务2->同步block2->任务4。
  • 在同步block2的串行队列中添加了任务3,同步队列的执行顺序为:任务2->同步block2->任务4->任务3。此时就出现了死锁。因为同步block2的执行依赖于任务3完成,而任务3的执行又依赖于任务4的执行完成,而任务4的完成依赖于同步block2的执行完成。

扩展:如果在上题的基础上去掉任务4,会发生什么?
答:仍然是发生死锁。因为此时任务3等待的是block2执行完毕,而block2等待任务3执行完成。

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

下面代码的执行顺序是什么?

- (void)interview04{
    //并发队列
    dispatch_queue_t queue = dispatch_queue_create("com.HQ.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 无序)

分析:

  • 任务1 和 任务2由于是异步函数+并发队列,会开启线程,所以没有固定顺序,
  • 任务7、任务8、任务9也是异步函数+并发队列,会开启线程,所以没有固定顺序
  • 任务3是同步函数+并发队列,同步函数会阻塞主线程,但是也只会阻塞0,所以,可以确定的是 0一定在3之后,在789之前
  • 任务1、任务2、任务3复杂度相差不大,因此任务1、任务2、任务3执行无序。

下面代码中,队列的类型有几种?

//串行队列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.HQ.Queue", NULL);
    
//并发队列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.HQ.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);

答:队列类型共有两种:串行队列和并发队列,主队列系统提供的串行队列,全局队列是系统提供的并发队列。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335