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

2021-04-02  本文已影响0人  卡布奇诺_95d2

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");
});

注意:

函数与队列

函数

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

队列

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

队列.png

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

image.png

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

函数与队列的不同组合

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

分析:

【面试题 - 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 死锁

分析:

扩展:如果在上题的基础上去掉任务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 无序)

分析:

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

//串行队列 - 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);

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

上一篇下一篇

猜你喜欢

热点阅读