定义:
Grand Central Dispatch(GCD)是苹果公司开发的一套多核编程的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。它是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现,开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。
优点:
GCD可以将花费时间极其长的任务放到后台线程,可以改善应用的响应性能。
GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
GCD比较轻量级,使得在很多地方比单独创建消耗内存的线程实用而且速度快。你可以不用担心太多的效率问题,而仅仅使用它就行了(就是告诉GCD我要执行什么任务,不需要编写任何线程管理代码)。
GCD的两个核心概念:任务和队列
任务:就是执行的操作,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。
队列:这里的队列指的是用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
任务:同步执行(sync)和异步执行(async)。
同步执行(sync):同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。只能在当前线程中执行任务,不具备开启新线程的能力。
异步执行(async):异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。可以在新的线程中执行任务,具备开启新线程的能力。但不一定开启新线程,比如:当前队列为主队列,异步执行就不会开启新的线程。
// 同步执行任务创建方法
dispatch_sync(queue, ^{// 这里放同步执行任务代码});
// 异步执行任务创建方法
dispatch_async(queue, ^{// 这里放异步执行任务代码});
队列:串行(Serial)队列和并发(Concurrent)队列。
串行(Serial):每次只有一个任务被执行,让任务一个接着一个地执行。
并发(Concurrent):可以让多个任务并发(同时)执行。
// 队列类型
dispatch_queue_t
// 创建队列
dispatch_queue_create(constchar*label,dispatch_queue_attr_tattr);
// 第一个参数:队列名称,表示队列的唯一标识符,用于 debug,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名。
// 第二个参数:队列类型,来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。如果输入NULL,表示串行队列,但建议不要写成NULL,可读性不好
// 串行队列的创建方法
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", 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(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
// 第一个参数:表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。
// 全局并发队列的优先级
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
//第二个参数暂时没用,用0即可。
GCD的使用步骤:
GCD 的使用步骤其实很简单,只有两步。
1.创建一个队列(串行队列或并发队列)。
2.将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)。
队列+任务组合
虽然使用GCD只需两步,但是既然有两种队列,两种任务执行方式,还有一种特殊的主队列,这样就有六种不同的组合方式。所有组合的特点:
异步执行 + 并行队列
- (void)asyncConcurrent {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t concurrentQueue =dispatch_queue_create("OC-Swift.ronglian.com",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
NSLog(@"1111====%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"2222====%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue, ^{
NSLog(@"3333====%@",[NSThread currentThread]);
});
NSLog(@"end");
}
输出结果:
2018-11-21 16:38:01.708601+0800 OC-Swift[5237:203796]
2018-11-21 16:38:01.708786+0800 OC-Swift[5237:203796] begin
2018-11-21 16:38:01.708912+0800 OC-Swift[5237:203796] end
2018-11-21 16:38:01.709163+0800 OC-Swift[5237:203854] 3333====
2018-11-21 16:38:01.709186+0800 OC-Swift[5237:203844] 1111====
2018-11-21 16:38:01.709164+0800 OC-Swift[5237:203846] 2222====
结果分析:
除了当前线程外,系统又开启了3个线程,并且任务是交替/同时执行的。(异步执行具备开启新线程的能力。且并发队列可开启多个线程,同时执行多个任务)。所有任务是在打印的begin和end之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务)。
异步执行 + 串行队列
- (void)asyncSerial {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_tserialQueue =dispatch_queue_create("OC-Swift.ronglian.com",DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"1111====%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"2222====%@",[NSThread currentThread]);
});
dispatch_async(serialQueue, ^{
NSLog(@"3333====%@",[NSThread currentThread]);
});
NSLog(@"end");
}
输出结果:
2018-11-21 16:45:35.134618+0800 OC-Swift[5319:207082]
2018-11-21 16:45:35.134847+0800 OC-Swift[5319:207082] begin
2018-11-21 16:45:35.135026+0800 OC-Swift[5319:207082] end
2018-11-21 16:45:35.135322+0800 OC-Swift[5319:207129] 1111====
2018-11-21 16:45:35.135452+0800 OC-Swift[5319:207129] 2222====
2018-11-21 16:45:35.135579+0800 OC-Swift[5319:207129] 3333====
结果分析:
除了当前线程外,开启了一条新线程(异步执行具备开启新线程的能力,串行队列只开启一个线程)。所有任务是在打印的begin和end之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。
异步执行 + 主队列
- (void)asyncMain {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
NSLog(@"1111====%@",[NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"2222====%@",[NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"3333====%@",[NSThread currentThread]);
});
NSLog(@"end");
}
输出结果:
2018-11-21 17:03:05.425973+0800 OC-Swift[5523:215111]
2018-11-21 17:03:05.426163+0800 OC-Swift[5523:215111] begin
2018-11-21 17:03:05.426295+0800 OC-Swift[5523:215111] end
2018-11-21 17:03:05.460636+0800 OC-Swift[5523:215111] 1111====
2018-11-21 17:03:05.460774+0800 OC-Swift[5523:215111] 2222====
2018-11-21 17:03:05.460879+0800 OC-Swift[5523:215111] 3333====
结果分析:
所有任务都是在当前线程中执行的,并没有开启新的线程(虽然异步执行具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中)。所有任务是在打印的begin和end之后才开始执行的(异步执行不会做任何等待,可以继续执行任务)。任务是按顺序执行的(因为主队列是串行队列,每次只有一个任务被执行,任务一个接一个按顺序执行)。
同步执行 + 并行队列
- (void)syncConcurrent {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_tconcurrentQueue =dispatch_queue_create("OC-Swift.ronglian.com",DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
NSLog(@"1111====%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"2222====%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue, ^{
NSLog(@"3333====%@",[NSThread currentThread]);
});
NSLog(@"end");
}
输出结果:
2018-11-21 16:55:01.751660+0800 OC-Swift[5434:211713]
2018-11-21 16:55:01.751830+0800 OC-Swift[5434:211713] begin
2018-11-21 16:55:01.751969+0800 OC-Swift[5434:211713] 1111====
2018-11-21 16:55:01.752082+0800 OC-Swift[5434:211713] 2222====
2018-11-21 16:55:01.752212+0800 OC-Swift[5434:211713] 3333====
2018-11-21 16:55:01.752314+0800 OC-Swift[5434:211713] end
结果分析:
所有任务都是在当前线程中执行的,没有开启新的线程(同步执行不具备开启新线程的能力)。所有任务都在打印的begin和end之间执行的(同步任务需要等待队列的任务执行结束)。任务按顺序执行的。按顺序执行的原因:在这里即便是并行队列,任务可以同时执行,但是由于只存在一个线程,所以没法把任务分发到不同的线程去同步处理,其结果就是只能在主线程里按顺序依次执行。
同步执行 + 串行队列
- (void)syncSerial {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_tserialQueue =dispatch_queue_create("OC-Swift.ronglian.com",DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
NSLog(@"1111====%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"2222====%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
NSLog(@"3333====%@",[NSThread currentThread]);
});
NSLog(@"end");
}
输出结果:
2018-11-21 16:51:40.264059+0800 OC-Swift[5378:209569]
2018-11-21 16:51:40.264236+0800 OC-Swift[5378:209569] begin
2018-11-21 16:51:40.264383+0800 OC-Swift[5378:209569] 1111====
2018-11-21 16:51:40.264474+0800 OC-Swift[5378:209569] 2222====
2018-11-21 16:51:40.264578+0800 OC-Swift[5378:209569] 3333====
2018-11-21 16:51:40.264663+0800 OC-Swift[5378:209569] end
结果分析:
所有任务都是在当前线程中执行的,并没有开启新的线程(同步执行不具备开启新线程的能力)。所有任务都在打印的begin和end之间执行(同步任务需要等待队列的任务执行结束)。任务是按顺序执行的(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)。
同步执行 + 主队列
- (void)syncMain {
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"begin");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
NSLog(@"1111====%@",[NSThread currentThread]);
});
dispatch_sync(mainQueue, ^{
NSLog(@"2222====%@",[NSThread currentThread]);
});
dispatch_sync(mainQueue, ^{
NSLog(@"3333====%@",[NSThread currentThread]);
});
NSLog(@"end");
}
输出结果:
2018-11-21 17:08:18.537677+0800 OC-Swift[5574:217088]
2018-11-21 17:08:18.537832+0800 OC-Swift[5574:217088] begin
结果分析:
在主线程中使用同步执行 + 主队列,追加到主线程的任务1、任务2、任务3都不再执行了,而且end也没有打印,在XCode 9上还会报崩溃。主要原因是当前的队列是主队列,主队列中加了一个同步执行的任务。这个同步执行的任务必须要这个主队列执行完成才执行,而主队列要执行完成必须要同步任务执行完成才行,就互相等待死锁了,然后就崩溃了。
类似于这样
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"deadlock");
});
执行上面的代码,你会发现没有任何打印,这个时候就是发生了死锁,我们禁止在主队列中,在同步使用主队列执行任务。同理,禁止在同一个同步串行队列中,再使用该串行队列同步的执行任务,因为这样会造成死锁。比如:
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"1111");
dispatch_sync(queue, ^{
NSLog(@"2222");
});
NSLog(@"3333");
});
NSLog(@"4444");
只输出“1111”就发生了死锁,导致程序崩溃。主要原因是当前的队列是串行队列,串行列中加了一个同步执行的任务。这个同步执行的任务必须要这个串行队列执行完成才执行,而串行队列要执行必须要同步任务执行完成才行,就互相等待了,造成死锁。