1、GCD的简介及基本概念
什么是GCD?
百度百科的解释为:GCD为Grand Central Dispatch的缩写。
- Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。
- 纯C语言,提供了非常多强大的函数
GCD有什么优势?
- GCD是苹果公司为多核的并行运算提供的解决方案
- GCD会自动利用更多的CPU内核 (例如:双核、四核)
- GCD会自动管理线程的生命周期 (创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任务线程管理代码
2、GCD的任务和队列
2.1 GCD有两个核心概念:
- 任务:执行什么操作
- 队列:用来存放任务
GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的执行遵循队列的FIFO规则:先进先出,后进后出
2.2 GCD的使用步骤
GCD的使用步骤仅仅只有两步:
1、创建一个队列(串行队列或并发队列)
2、将创建的任务添加到任务的等待队列中,系统会根据任务的执行方式来确定任务的执行(同步执行或异步执行)
* 创建队列:
创建队列的方法很简单,使用dispatch_queue_create
来创建队列,创建队列时有两个参数,第一个参数代表队列的唯一标识符,用来标记此队列,可填可不填。第二个参数是队列的方式,即:串行队列或并发队列。DISPATCH_QUEUE_SERIAL
代表串行队列,DISPATCH_QUEUE_CONCURRENT
代表并发队列。具体实现方式如下:
// 创建串行队列的方法
dispatch_queue_t queue = dispatch_queue_create("com.jerky.GCD", DISPATCH_QUEUE_SERIAL);
// 创建并发队列的方法
dispatch_queue_t queue = dispatch_queue_create("com.jerky.GCD", DISPATCH_QUEUE_CONCURRENT);
串行队列中有一种特殊的队列:主队列,主队列的获取方法是dispatch_get_main_queue();
主队列的特点:所有放在主队列中的任务,都会放在主线程中执行。
// 主队列的获取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue();
并发队列中,GCD默认提供了一种全局的并发队列:dispatch_get_global_queue
获取全局并发队列需要传入两个参数,第一个参数代表队列的优先级,一般传入DISPATCH_QUEUE_PRIORITY_DEFAULT
,第二个参数暂时没用,用0
即可
// 获取全局并发队列的方法
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
* 创建任务:
GCD的任务执行有两种方式,分别为同步执行和异步执行,相应的创建方法如下:
// 同步执行任务的创建方法
dispatch_sync(queue, ^{
// 需要同步执行的代码
});
// 异步执行任务的创建方法
dispatch_async(queue, ^{
// 需要异步执行的代码
});
3、GCD的基本使用
我们都知道GCD的执行方式分为同步执行
和异步执行
,队列分为串行队列
和并发队列
,再加上主队列
,所以组合起来有6种使用方式:
- 同步执行 + 串行队列
- 同步执行 + 并发队列
- 异步执行 + 串行队列
- 异步执行 + 并发队列
- 同步执行 + 主队列
- 异步执行 + 主队列
创建队列
dispatch_queue_t queue = dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>);
上面的创建队列的方法中,第一个参数代表:
下面我们来一个一个分析其使用情况
3.1 同步执行 + 串行队列
废话不多说,直接上代码:
// 同步 + 串行
- (void)syncSerial {
NSLog(@"start-currentThread--%@", [NSThread currentThread]);
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.jerky.gcd", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"1-currentThread--%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-currentThread--%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-currentThread--%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"4-currentThread--%@", [NSThread currentThread]);
});
NSLog(@"end-currentThread--%@", [NSThread currentThread]);
}
上述代码输出结果如下
2018-03-12 14:45:08.633588+0800 iOS多线程应用[1479:358360] start-currentThread--{number = 1, name = main}
2018-03-12 14:45:08.633758+0800 iOS多线程应用[1479:358360] 1-currentThread--{number = 1, name = main}
2018-03-12 14:45:08.633859+0800 iOS多线程应用[1479:358360] 2-currentThread--{number = 1, name = main}
2018-03-12 14:45:08.633943+0800 iOS多线程应用[1479:358360] 3-currentThread--{number = 1, name = main}
2018-03-12 14:45:08.634045+0800 iOS多线程应用[1479:358360] 4-currentThread--{number = 1, name = main}
2018-03-12 14:45:08.634130+0800 iOS多线程应用[1479:358360] end-currentThread--{number = 1, name = main}
从上面的输出结果可以看出来:
- 所有的执行任务都是在当前线程执行的,没有开启新线程。即:同步执行没有开启新线程的能力
- 所有的任务都是在
start
和end
之间执行的。即:同步任务需要等待一个任务执行完以后才继续往下执行下一个任务- 所有的任务都是按顺序执行的。即:串行队列的是一个一个往下执行的
3.2 同步执行 + 并发队列
// 同步 + 并行
- (void)syncConcurrent {
NSLog(@"start-currentThread--%@", [NSThread currentThread]);
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.jerky.gcd", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"1-currentThread--%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-currentThread--%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-currentThread--%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"4-currentThread--%@", [NSThread currentThread]);
});
NSLog(@"end-currentThread--%@", [NSThread currentThread]);
}
代码输出结果如下:
2018-03-12 14:56:04.834789+0800 iOS多线程应用[1513:390943] start-currentThread--{number = 1, name = main}
2018-03-12 14:56:04.834985+0800 iOS多线程应用[1513:390943] 1-currentThread--{number = 1, name = main}
2018-03-12 14:56:04.835098+0800 iOS多线程应用[1513:390943] 2-currentThread--{number = 1, name = main}
2018-03-12 14:56:04.835197+0800 iOS多线程应用[1513:390943] 3-currentThread--{number = 1, name = main}
2018-03-12 14:56:04.835290+0800 iOS多线程应用[1513:390943] 4-currentThread--{number = 1, name = main}
2018-03-12 14:56:04.835407+0800 iOS多线程应用[1513:390943] end-currentThread--{number = 1, name = main}
从上面的输出结果可以看出来:
- 所有的执行任务都是在当前线程执行的,没有开启新线程。即:同步执行没有开启新线程的能力
- 所有的任务都是在
start
和end
之间执行的。- 所有的任务都是按顺序执行的,虽然是
并发队列
,但是由于没有开启新线程,只能在当前线程执行任务,所以任务得一个一个的往下执行。
3.3 异步执行 + 串行队列
// 异步 + 串行
- (void)asyncSerial {
NSLog(@"start-currentThread--%@", [NSThread currentThread]);
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.jerky.gcd", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"1-currentThread--%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-currentThread--%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-currentThread--%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4-currentThread--%@", [NSThread currentThread]);
});
NSLog(@"end-currentThread--%@", [NSThread currentThread]);
}
代码输出结果如下:
2018-03-12 15:11:02.563906+0800 iOS多线程应用[1566:437155] start-currentThread--{number = 1, name = main}
2018-03-12 15:11:02.564162+0800 iOS多线程应用[1566:437155] end-currentThread--{number = 1, name = main}
2018-03-12 15:11:02.564187+0800 iOS多线程应用[1566:437245] 1-currentThread--{number = 3, name = (null)}
2018-03-12 15:11:02.564316+0800 iOS多线程应用[1566:437245] 2-currentThread--{number = 3, name = (null)}
2018-03-12 15:11:02.564446+0800 iOS多线程应用[1566:437245] 3-currentThread--{number = 3, name = (null)}
2018-03-12 15:11:02.564574+0800 iOS多线程应用[1566:437245] 4-currentThread--{number = 3, name = (null)}
从上面的输出结果可以看出来:
- 开启了一条新线程。即:异步执行具有开启新线程的能力,但串行队列只能开启一条线程
- 任务不是在
start
和end
之间执行的。即:主线程不需要等待,任务在新线程中执行,不影响主线程执行流程。- 所有的任务都是在新线程中按顺序执行的,因为队列时
串行队列
。
3.4 异步执行 + 并发队列
// 异步 + 并行
- (void)asyncConcurrent {
NSLog(@"start-currentThread--%@", [NSThread currentThread]);
// 并行队列
dispatch_queue_t queue = dispatch_queue_create("concurrentQueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"1-currentThread--%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-currentThread--%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-currentThread--%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"4-currentThread--%@", [NSThread currentThread]);
});
NSLog(@"end-currentThread--%@", [NSThread currentThread]);
}
代码输出结果如下:
2018-03-12 15:17:23.401366+0800 iOS多线程应用[1630:461242] start-currentThread--{number = 1, name = main}
2018-03-12 15:17:23.401831+0800 iOS多线程应用[1630:461242] end-currentThread--{number = 1, name = main}
2018-03-12 15:17:23.401856+0800 iOS多线程应用[1630:461377] 1-currentThread--{number = 3, name = (null)}
2018-03-12 15:17:23.401859+0800 iOS多线程应用[1630:461364] 2-currentThread--{number = 4, name = (null)}
2018-03-12 15:17:23.401859+0800 iOS多线程应用[1630:461362] 3-currentThread--{number = 5, name = (null)}
2018-03-12 15:17:23.401875+0800 iOS多线程应用[1630:461363] 4-currentThread--{number = 6, name = (null)}
从上面的输出结果可以看出来:
- 除了主线程,又开启了其他四条线程,并且任务是同时执行的
- 所有任务是在打印的start和end之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务
3.5 同步执行 + 主队列
-
在主线程中调用
同步执行 + 主队列
// 同步 + 主队列
- (void)syncMainQueue {
NSLog(@"start-currentThread---%@",[NSThread currentThread]); // 打印当前线程
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"queue---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
NSLog(@"queue---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_sync(queue, ^{
NSLog(@"queue---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"end-currentThread---%@",[NSThread currentThread]); // 打印当前线程
}
代码输出结果如下:
2018-03-12 16:02:43.892376+0800 iOS多线程应用[1866:554662] start-currentThread---{number = 1, name = main}
(lldb)
从上面的输出结果可以看出,在代码执行第一个任务时就直接崩溃了。原因是因为主线程中syncMainQueue
与任务1都在等待对方执行完毕才执行,这样你等我,我等你所以就没有结果,造成程序崩溃。具体原因:当程序运行时,主线程会把syncMainQueue
任务添加到主队列中,然后执行任务,因为主线程是同步执行,任务执行完成才执行下一个任务,这时我们又在主队列中添加了一个新任务,这时,新任务等待syncMainQueue
执行完再执行,然而syncMainQueue
任务等待新任务执行完再执行,这样互相等待对方执行完成,所以造成程序崩溃。
-
在其他线程中调用
同步执行 + 主队列
// 使用NSThread创建一个新线程,并自动执行
[NSThread detachNewThreadSelector:@selector(syncMainQueue) toTarget:self withObject:nil];
代码输出结果如下:
2018-03-12 16:16:49.061596+0800 iOS多线程应用[1920:597755] start-currentThread---{number = 3, name = (null)}
2018-03-12 16:16:49.063974+0800 iOS多线程应用[1920:597598] queue---{number = 1, name = main}
2018-03-12 16:16:49.064469+0800 iOS多线程应用[1920:597598] queue---{number = 1, name = main}
2018-03-12 16:16:49.064925+0800 iOS多线程应用[1920:597598] queue---{number = 1, name = main}
2018-03-12 16:16:49.065049+0800 iOS多线程应用[1920:597755] end-currentThread---{number = 3, name = (null)}
根据输出结果可以得出以下结论:
- 所有任务都是在主线程中执行的。即:没有开启新线程,所有放在主队列中的任务都会在主线程中执行;
- 任务都是在start和end之间执行的。即:同步队列需要等待任务执行完毕再执行;
- 所有的任务都是按顺序执行的。即:主队列是串行队列,任务是一个一个的执行的,每次执行一个任务。
3.5 异步执行 + 主队列
//异步 + 主队列
- (void)asyncMainQueue {
NSLog(@"start-currentThread---%@",[NSThread currentThread]); // 打印当前线程
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
NSLog(@"queue---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
NSLog(@"queue---%@",[NSThread currentThread]); // 打印当前线程
});
dispatch_async(queue, ^{
NSLog(@"queue---%@",[NSThread currentThread]); // 打印当前线程
});
NSLog(@"end-currentThread---%@",[NSThread currentThread]); // 打印当前线程
}
代码输出结果如下:
2018-03-12 16:25:06.024259+0800 iOS多线程应用[1958:624690] start-currentThread---{number = 1, name = main}
2018-03-12 16:25:06.024421+0800 iOS多线程应用[1958:624690] end-currentThread---{number = 1, name = main}
2018-03-12 16:25:06.027317+0800 iOS多线程应用[1958:624690] queue---{number = 1, name = main}
2018-03-12 16:25:06.027441+0800 iOS多线程应用[1958:624690] queue---{number = 1, name = main}
2018-03-12 16:25:06.027553+0800 iOS多线程应用[1958:624690] queue---{number = 1, name = main}
根据输出结果可以得出以下结论:
- 所有任务都是在主线程中执行的。即:即使是
异步执行
,但是在主队列中执行,也没有开启新线程的能力;- 所有任务都是在start和end之后执行的。即:
异步执行
不会等待任务执行;- 所有任务都是按顺序执行的。即:主队列是
串行队列
。
根据上面的6种方式,我们可以总结出如下表格:
4、 GCD 线程间的通信
// 线程间的通信
- (void)threadCommunicate {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行耗时的异步操作
NSLog(@"1-currentThread--%@", [NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
// 耗时操作实行完成后,回到主线程
NSLog(@"2-currentThread--%@", [NSThread currentThread]);
});
});
}
代码输出结果如下:
2018-03-12 16:44:36.051995+0800 iOS多线程应用[2088:692945] 1-currentThread--{number = 3, name = (null)}
2018-03-12 16:44:36.054571+0800 iOS多线程应用[2088:692795] 2-currentThread--{number = 1, name = main}
5、GCD队列组
假如在开发中有这么一个需求:
- 分别先执行两个耗时操作
- 然后等两个耗时操作都执行完成后再回到主线程中执行其他操作
如果想要高效快速的实现此需求,可以使用队列组来实现:
废话不多说,直接上代码
// 队列组
- (void)queueGroup {
NSLog(@"1-currentThread--%@", [NSThread currentThread]);
dispatch_group_t groupQueue = dispatch_group_create();
dispatch_group_async(groupQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行第一个耗时操作
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"2-currentThread--%@", [NSThread currentThread]);
}
});
dispatch_group_async(groupQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行第二个耗时操作
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"3-currentThread--%@", [NSThread currentThread]);
}
});
dispatch_group_notify(groupQueue, dispatch_get_main_queue(), ^{
// 两个耗时操作执行完毕后,回到主线程执行操作
NSLog(@"4-currentThread--%@", [NSThread currentThread]);
});
}
代码输出结果如下:
2018-03-12 16:55:57.627077+0800 iOS多线程应用[2222:729717] 1-currentThread--{number = 1, name = main}
2018-03-12 16:55:57.627505+0800 iOS多线程应用[2222:729855] 2-currentThread--{number = 3, name = (null)}
2018-03-12 16:55:57.627513+0800 iOS多线程应用[2222:729851] 3-currentThread--{number = 4, name = (null)}
2018-03-12 16:55:57.627846+0800 iOS多线程应用[2222:729851] 3-currentThread--{number = 4, name = (null)}
2018-03-12 16:55:57.627861+0800 iOS多线程应用[2222:729855] 2-currentThread--{number = 3, name = (null)}
2018-03-12 16:55:57.628045+0800 iOS多线程应用[2222:729851] 3-currentThread--{number = 4, name = (null)}
2018-03-12 16:55:57.628087+0800 iOS多线程应用[2222:729855] 2-currentThread--{number = 3, name = (null)}
2018-03-12 16:55:57.634468+0800 iOS多线程应用[2222:729717] 4-currentThread--{number = 1, name = main}