目录
- GCD简介
- GCD核心概念
- GCD队列的使用
- GCD的常见面试题
GCD简介
Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。
脑图
GCD整体结构
GCD相关的一些基本概念
GCD 信号量:dispatch_semaphore
优点
- 可用于多核的并行运算;
- 会自动利用更多的CPU内核(比如双核、四核);
- 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
- 抽象层次最高,使用也简单,因此,苹果也推荐使用GCD。
缺点:
- 使用不当容易造成死锁。
GCD核心概念
任务和队列
-
任务:就是你在线程中执行的那段代码,放在GCD的Block块中。执行任务有2种方式:
同步执行(sync)
和异步执行(async)
。
同步执行(sync):
1. 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
2. 只能在当前线程中执行任务,不具备开启新线程的能力。
异步执行(async):
1. 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
2. 可以在新的线程中执行任务,具备开启新线程的能力。
通俗解释:
人物:你,我;
事件:吃饭;
同步:你叫我吃饭,我听到回复你,然后一起去吃饭。如果我没有听到或者回复你,你就会一直叫我去吃饭,一直等我,直到我和你一起去吃饭为止。
异步:你去吃饭,喊了我一声,然后自己直接去吃饭了,我的行为不会对你造成影响。我可以和你一起去吃,也可以等我把手头的事情忙完再去吃。这之间你可以完成很多事情,不会受到我的影响。
区别
:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
-
队列(Dispatch Queue):队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。
GCD有2种队列:串行队列(Serial Dispatch Queue)
和并发队列(Concurrent Dispatch Queue)
。
串行队列(Serial Dispatch Queue):每次只执行一个任务,当前一个任务执行完成后才执行下一个任务。
并发队列(Concurrent Dispatch Queue):多个任务并发执行,所以先执行的任务可能最后才完成(因为具体的执行过程导致)。
区别
:执行顺序不同,以及开启线程数不同。
具体区别如下两图所示(图片来源网络):
关于
同步异步、串行并行和线程
的关系,下面通过一个表格来总结:由上图我们可以得出我们有3种队列,2种任务执行方式,那么我们就有了6种不同的组合方式,分别是:
1.并行队列 + 同步执行
2.并行队列 + 异步执行
3.串行队列 + 同步执行
4.串行队列 + 异步执行
5.主队列 + 同步执行
6.主队列 + 异步执行
那么这几种不同组合方式各有什么区别:
队列 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步 (sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 主线程调用:死锁卡住不执行其他线程调用:没有开启新线程,串行执行任务 |
异步 (async) | 有开启新线程,并发执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
需要注意
的几个地方:
- 异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。
- 并发队列的并发功能只有在异步(dispatch_async)函数下才有效。
- 主队列是一种特殊的串行队列。
GCD队列的使用
- 队列的创建:
可以使用dispatch_queue_create
来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL
表示串行队列,DISPATCH_QUEUE_CONCURRENT
表示并行队列。
// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_SERIAL);
// 并行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
// 全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
- 队列的获取(这里说的是两个特殊的队列,主队列和全局并发队列):
主队列:
dispatch_queue_t queue = dispatch_get_main_queue();
并发队列:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 任务的创建:
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 执行的任务
NSLog(@"%@",[NSThread currentThread]);
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 执行的任务
NSLog(@"%@",[NSThread currentThread]);
});
需要注意
的几个地方:
- 所有放在主队列中的任务,都会放到主线程中执行。
- GCD 默认提供了全局并发队列(Global Dispatch Queue)。
- 6种不同的组合方式:
1.并发队列 + 同步执行
特点
:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
//并发队列 + 同步执行
- (void) syncConcurrent
{
NSLog(@"begin-- 并发队列 + 同步执行");
dispatch_queue_t queue= dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 并发队列 + 同步执行");
}
输出的结果:
2018-09-20 17:15:06.116567+0800 GCDDemo[2295:1059495] begin-- 并发队列 + 同步执行
2018-09-20 17:15:06.116978+0800 GCDDemo[2295:1059495] 1------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117130+0800 GCDDemo[2295:1059495] 1------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117269+0800 GCDDemo[2295:1059495] 2------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117387+0800 GCDDemo[2295:1059495] 2------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117467+0800 GCDDemo[2295:1059495] 3------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117536+0800 GCDDemo[2295:1059495] 3------<NSThread: 0x60000250d3c0>{number = 1, name = main}
2018-09-20 17:15:06.117609+0800 GCDDemo[2295:1059495] end-- 并发队列 + 同步执行
从打印中可以总结如下几点:
- 所有任务都是在当前线程(主线程)中执行的,没有开启新的线程(
同步执行
不具备开启新线程的能力)。 - 所有任务按顺序执行的。
- 所有任务都在打印的begin--和end--之间,由于同步任务需要等待队列的任务执行结束才能执行下一个任务。
疑问点
:并发队列具备开启多个线程能力为什么没不能同时执行任务呢?
答
:虽然并发队列可以开启多个线程,并且可以同时执行多个任务。但是其本身不能创建新线程,因为同步任务不具备开启新线程的能力
,所以只有当前线程这一个线程,所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束
)。所以任务只能一个接一个按顺序执行,不能同时被执行。
2.并发队列 + 异步执行
特点
:可以开启多个线程,任务交替(同时)执行。
// 并发队列 + 异步执行
- (void) asyncConcurrent
{
NSLog(@"begin-- 并发队列 + 异步执行");
dispatch_queue_t queue= dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 并发队列 + 异步执行");
}
输出的结果:
2018-09-20 17:41:42.367277+0800 GCDDemo[2336:1126287] begin-- 并发队列 + 异步执行
2018-09-20 17:41:42.367446+0800 GCDDemo[2336:1126287] end-- 并发队列 + 异步执行
2018-09-20 17:41:42.367505+0800 GCDDemo[2336:1126603] 2------<NSThread: 0x60000083a280>{number = 4, name = (null)}
2018-09-20 17:41:42.367517+0800 GCDDemo[2336:1126606] 3------<NSThread: 0x600000827d00>{number = 5, name = (null)}
2018-09-20 17:41:42.367522+0800 GCDDemo[2336:1126604] 1------<NSThread: 0x600000827c00>{number = 3, name = (null)}
2018-09-20 17:41:42.367601+0800 GCDDemo[2336:1126603] 2------<NSThread: 0x60000083a280>{number = 4, name = (null)}
2018-09-20 17:41:42.367627+0800 GCDDemo[2336:1126606] 3------<NSThread: 0x600000827d00>{number = 5, name = (null)}
2018-09-20 17:41:42.367653+0800 GCDDemo[2336:1126604] 1------<NSThread: 0x600000827c00>{number = 3, name = (null)}
从打印中可以总结如下几点:
- 除了主线程,又开启了3个线程,并且任务是交替着同时执行的,由于
异步执行
具备开启新线程的能力,且并发队列
可开启多个线程,同时执行多个任务. - 所有任务是在打印的begin-- 和end-- 之后才开始执行的,说明任务不是马上执行,而是将所有任务添加到队列之后才开始异步执行,另外当前线程并没有等待,而是直接开启了新的线程,在新线程中执行任务,由于
异步执行
不做等待,所以可以继续执行其他任务.
3.串行队列 + 同步执行
特点
:不会开启新线程,在当前线程执行任务,并且任务是串行的,执行完一个任务,再执行下一个任务。
// 串行队列 + 同步执行
- (void) syncSerial
{
NSLog(@"begin-- 串行队列 + 同步执行");
dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 串行队列 + 同步执行");
}
输出的结果:
2018-09-21 09:14:51.305711+0800 GCDDemo[1205:63899] begin-- 串行队列 + 同步执行
2018-09-21 09:14:51.305958+0800 GCDDemo[1205:63899] 1------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306117+0800 GCDDemo[1205:63899] 1------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306202+0800 GCDDemo[1205:63899] 2------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306292+0800 GCDDemo[1205:63899] 2------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306431+0800 GCDDemo[1205:63899] 3------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306516+0800 GCDDemo[1205:63899] 3------<NSThread: 0x6000005953c0>{number = 1, name = main}
2018-09-21 09:14:51.306626+0800 GCDDemo[1205:63899] end-- 串行队列 + 同步执行
从打印中可以总结如下几点:
- 所有任务都是在主线程中执行的,并没有开启新的线程(
同步执行
不具备开启新线程的能力),由于串行队列,所以按顺序一个一个执行下去。 - 所有任务都在打印的begin-- 和end-- 之间,这说明任务是添加到队列中马上执行的,而且
同步任务
需要等待队列的任务执行结束,才可以执行下一个任务。 - 任务是按顺序执行的,这说明
串行队列
每次只有一个任务被执行,任务一个接一个按顺序执行下去。
4.串行队列 + 异步执行
特点
:会开启新线程,但是因为任务是串行的,所以执行完一个任务之后,才会再执行下一个任务。
//串行队列 + 异步执行
- (void)asyncSerial {
NSLog(@"begin-- 串行队列 + 异步执行");
dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
// 追加任务3
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 串行队列 + 异步执行");
}
输出的结果:
2018-09-21 09:35:45.691409+0800 GCDDemo[1363:134940] begin-- 串行队列 + 异步执行
2018-09-21 09:35:45.691612+0800 GCDDemo[1363:134940] end-- 串行队列 + 异步执行
2018-09-21 09:35:45.691686+0800 GCDDemo[1363:135267] 1------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.691787+0800 GCDDemo[1363:135267] 1------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.691886+0800 GCDDemo[1363:135267] 2------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.691959+0800 GCDDemo[1363:135267] 2------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.692040+0800 GCDDemo[1363:135267] 3------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
2018-09-21 09:35:45.692107+0800 GCDDemo[1363:135267] 3------<NSThread: 0x6000016fc980>{number = 3, name = (null)}
从打印中可以总结如下几点:
- 开启了一条新线程(
异步执行
具备开启新线程的能力,但是串行队列
只能开启一个线程),但是任务由于是串行的,所以任务还是一个一个的执行下去(串行队列
每次只能有一个任务被执行,任务一个接着一个顺序执行)。 - 所有任务是在打印的begin-- 和end-- 之后才开始执行的,说明任务是将所有任务添加到队列之后才开始同步执行,而不是马上执行(
异步执行
不会做任何等待,可以继续执行任务)。
5.主队列 + 同步执行
特点
:1.主线程调用:互等卡主不执行。 2.其他线程调用:不会开启新线程,执行完一个任务,再执行下一个任务。
- 主线程调用
//主队列 + 同步执行
- (void)syncMain
{
NSLog(@"begin-- 主队列 + 同步执行");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 主队列 + 同步执行");
}
输出的结果:
2018-09-21 10:02:33.294677+0800 GCDDemo[1563:202647] begin-- 主队列 + 同步执行
(lldb)
从打印中可以总结如下:
- 程序崩溃,只打印出
begin-- 主队列 + 同步执行
疑问点
:为什么任务没有执行完,而且程序了崩溃呢?
答
:我们在主线程中执行syncMain
方法,相当于把syncMain任务
放到了主线程的队列中,主线程正在处理syncMain这个任务
而syncMain
方法中又有同步事件需要处理 ,造成会相互等待,所以死锁
了,所以任务不会执行完。
- 其他线程调用
// 我们先创建了一个异步并发队列,然后在队列中调用syncMain方法。
dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"当前线程:%@",[NSThread currentThread]);
[self syncMain];
});
输出的结果:
2018-09-21 10:52:54.968128+0800 GCDDemo[1926:297771] 当前线程:<NSThread: 0x6000023a23c0>{number = 3, name = (null)}
2018-09-21 10:52:54.976098+0800 GCDDemo[1926:297771] begin-- 主队列 + 同步执行
2018-09-21 10:52:54.978843+0800 GCDDemo[1926:297474] 1------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.978939+0800 GCDDemo[1926:297474] 1------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979112+0800 GCDDemo[1926:297474] 2------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979187+0800 GCDDemo[1926:297474] 2------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979789+0800 GCDDemo[1926:297474] 3------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979871+0800 GCDDemo[1926:297474] 3------<NSThread: 0x6000023993c0>{number = 1, name = main}
2018-09-21 10:52:54.979958+0800 GCDDemo[1926:297771] end-- 主队列 + 同步执行
从打印中可以总结如下:
- 所有
任务
都是在主线程
中执行的,并没有开启新的线程(所有放在主队列
中的任务,都会放到主线程
中执行),而且由于主队列
是串行
队列,所以按顺序
一个一个执行。 - 所有任务都在打印的begin-- 和end-- 之间,这说明任务是添加到队列中马上执行的。
疑问点
:为什么在其他线程调用不会崩溃卡住呢?
答
:因为syncMain
任务放到了其他线程里,而syncMain
方法中的几个任务都追加到主队列中,因为主队列现在没有正在执行的任务,所以会直接执行主队列的任务,一个个执行下去,所以这里不会卡住线程。
6.主队列 + 异步执行
特点
:只在主线程中执行任务,执行完一个任务,再执行下一个任务。
//主队列 + 异步执行
- (void)asyncMain
{
NSLog(@"begin-- 主队列 + 异步执行");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 主队列 + 异步执行");
}
输出的结果:
2018-09-21 11:13:55.685841+0800 GCDDemo[2099:354235] begin-- 主队列 + 异步执行
2018-09-21 11:13:55.686023+0800 GCDDemo[2099:354235] end-- 主队列 + 异步执行
2018-09-21 11:13:55.695339+0800 GCDDemo[2099:354235] 1------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695555+0800 GCDDemo[2099:354235] 1------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695661+0800 GCDDemo[2099:354235] 2------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695733+0800 GCDDemo[2099:354235] 2------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695795+0800 GCDDemo[2099:354235] 3------<NSThread: 0x600000c653c0>{number = 1, name = main}
2018-09-21 11:13:55.695852+0800 GCDDemo[2099:354235] 3------<NSThread: 0x600000c653c0>{number = 1, name = main}
从打印中可以总结如下:
1.
所有任务都是在主线程
中执行的,并没有开启新的线程,虽然异步执行
具备开启线程的能力,但因为是主队列,所以所有任务都在主队列(主队列是串行队列)中,并且一个接一个的执行下去.
2.
所有任务是在打印的begin-- 和end-- 之后才开始执行的,说明任务并不是马上执行,而是将所有任务添加到队列之后才开始同步执行。
- GCD其他方法
1.栅栏方法(dispatch_barrier_async和dispatch_barrier_sync)
//dispatch_barrier_async
- (void) asyncbarrier
{
dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
NSLog(@"aa, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
NSLog(@"bb, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
}
输出的结果:
2018-09-21 14:36:35.946008+0800 GCDDemo[3665:625310] aa, <NSThread: 0x600002f013c0>{number = 1, name = main}
2018-09-21 14:36:35.946012+0800 GCDDemo[3665:625532] ----1-----<NSThread: 0x600002f57dc0>{number = 3, name = (null)}
2018-09-21 14:36:35.946027+0800 GCDDemo[3665:625531] ----2-----<NSThread: 0x600002f5d3c0>{number = 4, name = (null)}
2018-09-21 14:36:35.946139+0800 GCDDemo[3665:625310] bb, <NSThread: 0x600002f013c0>{number = 1, name = main}
2018-09-21 14:36:35.946161+0800 GCDDemo[3665:625531] ----barrier-----<NSThread: 0x600002f5d3c0>{number = 4, name = (null)}
2018-09-21 14:36:35.946258+0800 GCDDemo[3665:625532] ----4-----<NSThread: 0x600002f57dc0>{number = 3, name = (null)}
2018-09-21 14:36:35.946277+0800 GCDDemo[3665:625531] ----3-----<NSThread: 0x600002f5d3c0>{number = 4, name = (null)}
从打印中可以总结如下:
- aa和bb都在
主线程
进行输出。 - 先执行完
barrier
之前的任务,然后再执行自己的任务(barrier
),最后执行barrier
之后的任务(注意这里说的是任务不是下一行代码)。✨✨✨ - 将自己的任务(
barrier
)插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务(3、4
)插入到队列,然后执行任务。✨✨✨
//dispatch_barrier_sync
- (void) syncbarrier
{
dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_barrier_sync(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
NSLog(@"aa, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
NSLog(@"bb, %@", [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
}
输出的结果:
2018-09-21 14:38:45.374566+0800 GCDDemo[3689:634918] ----2-----<NSThread: 0x6000032dfa80>{number = 4, name = (null)}
2018-09-21 14:38:45.374567+0800 GCDDemo[3689:634917] ----1-----<NSThread: 0x6000032d04c0>{number = 3, name = (null)}
2018-09-21 14:38:45.374780+0800 GCDDemo[3689:634674] ----barrier-----<NSThread: 0x60000328d3c0>{number = 1, name = main}
2018-09-21 14:38:45.374858+0800 GCDDemo[3689:634674] aa, <NSThread: 0x60000328d3c0>{number = 1, name = main}
2018-09-21 14:38:45.374939+0800 GCDDemo[3689:634674] bb, <NSThread: 0x60000328d3c0>{number = 1, name = main}
2018-09-21 14:38:45.374956+0800 GCDDemo[3689:634917] ----3-----<NSThread: 0x6000032d04c0>{number = 3, name = (null)}
2018-09-21 14:38:45.375039+0800 GCDDemo[3689:634917] ----4-----<NSThread: 0x6000032d04c0>{number = 3, name = (null)}
从打印中可以总结如下:
- aa和bb都在
主线程
进行输出。 - barrier队列里面任务在
主线程
中执行。 - 先执行完
barrier
之前的任务,然后再执行自己的任务(barrier
),最后执行barrier
之后的任务(注意这里说的是任务不是下一行代码
)。✨✨✨ - 需要等待自己的任务(
barrier
)结束之后,才会继续添加并执行写在barrier后面的任务(3、4
),然后执行后面的任务。✨✨✨
注意点:
在使用栅栏函数时,需使用自定义队列
才有意义,如果用的是串行队列
或者系统提供的全局并发队列
,这个栅栏函数
的作用等同于
一个同步函数
的作用。
dispatch_barrier_async
和dispatch_barrier_sync
异同点总结:
相同点:
- 先执行完
barrier
之前的任务,然后再执行自己的任务(barrier
),最后执行barrier
之后的任务(注意这里说的是任务不是下一行代码
)。
不同点:
-
dispatch_barrier_async
将自己的任务(barrier)插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务(3、4)插入到队列,然后执行任务。 -
dispatch_barrier_sync
需要等待自己的任务(barrier)结束之后,才会继续添加并执行写在barrier后面的任务(3、4),然后执行后面的任务。
2.延时执行方法(dispatch_after)
//dispatch_after
- (void)after {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"run-----%@",[NSThread currentThread]);
});
}
输出的结果:
2018-09-25 14:06:01.709795+0800 GCDDemo[6349:555122] run-----<NSThread: 0x600002046940>{number = 1, name = main}
需要注意
的地方:
-
dispatch_after
函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中,所以严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after
函数是很有效的。
3.快速迭代方法(dispatch_apply)
//dispatch_apply
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"begin-- ");
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"end--");
}
输出的结果:
2018-09-25 14:23:48.028596+0800 GCDDemo[6495:604219] begin--
2018-09-25 14:23:48.028858+0800 GCDDemo[6495:604219] 0---<NSThread: 0x6000003613c0>{number = 1, name = main}
2018-09-25 14:23:48.028874+0800 GCDDemo[6495:604631] 1---<NSThread: 0x60000032f880>{number = 3, name = (null)}
2018-09-25 14:23:48.028894+0800 GCDDemo[6495:604632] 2---<NSThread: 0x6000003034c0>{number = 4, name = (null)}
2018-09-25 14:23:48.028917+0800 GCDDemo[6495:604630] 3---<NSThread: 0x600000303680>{number = 5, name = (null)}
2018-09-25 14:23:48.028944+0800 GCDDemo[6495:604219] 4---<NSThread: 0x6000003613c0>{number = 1, name = main}
2018-09-25 14:23:48.028961+0800 GCDDemo[6495:604631] 5---<NSThread: 0x60000032f880>{number = 3, name = (null)}
2018-09-25 14:23:48.029002+0800 GCDDemo[6495:604632] 6---<NSThread: 0x6000003034c0>{number = 4, name = (null)}
2018-09-25 14:23:48.029018+0800 GCDDemo[6495:604630] 7---<NSThread: 0x600000303680>{number = 5, name = (null)}
2018-09-25 14:23:48.029059+0800 GCDDemo[6495:604219] 8---<NSThread: 0x6000003613c0>{number = 1, name = main}
2018-09-25 14:23:48.029071+0800 GCDDemo[6495:604631] 9---<NSThread: 0x60000032f880>{number = 3, name = (null)}
2018-09-25 14:23:48.029626+0800 GCDDemo[6495:604219] end--
需要注意
的地方:
- 如果在
串行队列
中使用dispatch_apply
,那么就和 for 循环一样,按顺序同步执行,所以一般都是在异步队列中使用。 - 因为是在
并发队列
中异步执行任务,各个任务的执行时间长短不定,所以最后结束顺序也不定(上面是顺序打印出来的,但是不一定就是顺序)。
4.只执行一次 ( dispatch_once)
//dispatch_once
- (void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
}
常用使用场景:单例
- GCD队列组(
dispatch_group
)
1.dispatch_group_notify
特点:
监听 所有添加到group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
// dispatch_group_notify
- (void)notify {
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(3);
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(4);
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步任务1、任务2、任务3都执行完毕后,回到主线程执行下边任务
NSLog(@"----4-----%@", [NSThread currentThread]);
NSLog(@"group---end");
});
NSLog(@"----5-----end");
}
输出的结果:
2018-09-25 16:33:18.057450+0800 GCDDemo[7462:922204] group---begin
2018-09-25 16:33:18.057673+0800 GCDDemo[7462:922204] ----5-----end
2018-09-25 16:33:18.057726+0800 GCDDemo[7462:922320] ----1-----<NSThread: 0x60000196a540>{number = 3, name = (null)}
2018-09-25 16:33:21.062976+0800 GCDDemo[7462:922311] ----2-----<NSThread: 0x6000019073c0>{number = 4, name = (null)}
2018-09-25 16:33:22.058039+0800 GCDDemo[7462:922308] ----3-----<NSThread: 0x600001921140>{number = 5, name = (null)}
2018-09-25 16:33:22.058223+0800 GCDDemo[7462:922204] ----4-----<NSThread: 0x600001961280>{number = 1, name = main}
2018-09-25 16:33:22.058290+0800 GCDDemo[7462:922204] group---end
从打印中可以总结如下:
- 当所有任务都执行完成之后,才执行
dispatch_group_notify
block 中的任务。 -
NSLog(@"----5-----end")
会先执行,不会等到dispatch_group_notify
都执行完了,才去执行。
2.dispatch_group_wait
特点:
等待group
关联的block执行完毕,才会继续往下执行。
//dispatch_group_wait
- (void)wait {
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
输出的结果:
2018-09-26 10:24:51.163832+0800 GCDDemo[1907:215711] group---begin
2018-09-26 10:24:51.164102+0800 GCDDemo[1907:215790] ----2-----<NSThread: 0x600003fc0700>{number = 3, name = (null)}
2018-09-26 10:24:51.164118+0800 GCDDemo[1907:215793] ----3-----<NSThread: 0x600003f9b180>{number = 4, name = (null)}
2018-09-26 10:24:53.164214+0800 GCDDemo[1907:215791] ----1-----<NSThread: 0x600003fed880>{number = 5, name = (null)}
2018-09-26 10:24:53.164357+0800 GCDDemo[1907:215711] group---end
从打印中可以总结如下:
- 执行完
group
关联的block之后,才继续往下执行的。 - 打印顺序不是顺序的,说明是
异步
执行,没有等待(感觉是废话
~)。
3.dispatch_group_enter和dispatch_group_leave
特点:``dispatch_group_enter
和dispatch_group_leave
总是成对出现的。
dispatch_group_enter:
用于添加对应任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数加1,当未执行完毕任务数为0的时候,才会使dispatch_group_wait
解除阻塞和dispatch_group_notify
的block执行。
dispatch_group_leave:
用于减少任务组中的未执行完毕的任务数,执行一次,未执行完毕的任务数减1,dispatch_group_enter
和dispatch_group_leave
要匹配,不然系统会认为group
任务没有执行完毕。
//dispatch_group_enter、dispatch_group_leave
- (void)enterAndLeave{
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
NSLog(@"group---end");
});
输出的结果:
2018-09-26 11:13:05.879685+0800 GCDDemo[2589:360180] group---begin
2018-09-26 11:13:05.880582+0800 GCDDemo[2589:360271] ----2-----<NSThread: 0x6000016580c0>{number = 4, name = (null)}
2018-09-26 11:13:05.880934+0800 GCDDemo[2589:360272] ----1-----<NSThread: 0x60000166a240>{number = 3, name = (null)}
2018-09-26 11:13:05.889769+0800 GCDDemo[2589:360180] ----3-----<NSThread: 0x600001634c00>{number = 1, name = main}
2018-09-26 11:13:05.889853+0800 GCDDemo[2589:360180] group---end
}
- GCD信号量(
dispatch_semaphore
)
定义:
信号量就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。其实,这有点类似锁机制了,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。
信号量主要有3个函数:
1.dispatch_semaphore_create(信号量值):
创建信号量,参数:信号量的初值,如果小于0则会返回NULL
。
2.dispatch_semaphore_wait(信号量,等待时间):
可以使总信号量减1。
3.dispatch_semaphore_signal(信号量):
发送一个信号,让信号总量加1。
下面详细说一下:
1.dispatch_semaphore_create
函数的声明为:
dispatch_samaphore_t dispatch_semaphore_create(long value);
传入的参数为long
,输出一个dispatch_semaphore_t
类型且值为value
的信号量。
值得注意的是,这里的传入的参数value
必须大于或等于0
,否则dispatch_semaphore_create
会返回NULL
。
2.dispatch_semaphore_signal
函数的声明为:
long dispatch_semaphore_signal(dispatch_semaphore_tdsema)
这个函数会使传入的信号量dsema
的值加1
。
3. dispatch_semaphore_wait
函数的声明为:
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
这个函数会使传入的信号量dsema
的值减1
。
这个函数的作用
是这样的,如果dsema
信号量的值大于0
,该函数所处线程就继续执行下面的语句,并且将信号量的值减1
;如果desema
的值为0
,那么这个函数就阻塞
当前线程等待timeout
(注意timeout
的类型为dispatch_time_t
,不能直接传入整型
或float
类型),如果等待的期间desema
的值被dispatch_semaphore_signal
函数加1
了,且该函数(即dispatch_semaphore_wait
)所处线程获得了信号量,那么就继续向下执行并将信号量减1
。如果等待期间没有获取到信号量或者信号量的值一直为0
,那么等到timeout
时,其所处线程自动执行其后语句。
4.dispatch_semaphore_signal和dispatch_semaphore_wait
的返回值
代表的意义(这里要注意函数返回值和dsema的值的区别
):
dispatch_semaphore_signal
当返回值为0
时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1
即可。当返回值不为0
时,表示其当前有(一个或多个
)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。
dispatch_semaphore_wait
的返回值也为long
类型。当其返回0
时表示在timeout
之前,该函数所处的线程被成功唤醒,当其返回不为0
时,表示timeout
发生。
5.设置timeout
比较有用的两个宏:DISPATCH_TIME_NOW
和 DISPATCH_TIME_FOREVER
,当然你也可以自己创建一个dispatch_time_t
类型的变量来使用。
例如:dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 1*1000*1000*1000);
6.关于信号量通俗的解释:
停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。
信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait
函数就相当于来了一辆车,dispatch_semaphore_signal
就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)
),调用一次dispatch_semaphore_signal
,剩余的车位就增加一个;调用一次dispatch_semaphore_wait
剩余车位就减少一个;当剩余车位为0
时,再来车(即调用dispatch_semaphore_wait
)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。
-(void)dispatchSignal{
//crate的value表示,最多几个资源可访问
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(quene, ^{
//任务1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"----1-----");
sleep(1);
NSLog(@"complete ----1-----");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(quene, ^{
//任务2
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"----2-----");
sleep(1);
NSLog(@"complete ----2-----");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(quene, ^{
//任务3
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"----3-----");
sleep(1);
NSLog(@"complete ----3-----");
dispatch_semaphore_signal(semaphore);
});
}
信号量的初始值设置为1时输出结果:
2018-09-26 16:54:52.756489+0800 GCDDemo[7192:1007207] ----1-----
2018-09-26 16:54:53.759805+0800 GCDDemo[7192:1007207] complete ----1-----
2018-09-26 16:54:53.759963+0800 GCDDemo[7192:1007211] ----2-----
2018-09-26 16:54:54.763736+0800 GCDDemo[7192:1007211] complete ----2-----
2018-09-26 16:54:54.763988+0800 GCDDemo[7192:1007209] ----3-----
2018-09-26 16:54:55.769148+0800 GCDDemo[7192:1007209] complete ----3-----
信号量的初始值设置为2时输出结果:
2018-09-26 16:56:10.535546+0800 GCDDemo[7231:1012058] ----1-----
2018-09-26 16:56:10.535565+0800 GCDDemo[7231:1012060] ----2-----
2018-09-26 16:56:11.540570+0800 GCDDemo[7231:1012060] complete ----2-----
2018-09-26 16:56:11.540585+0800 GCDDemo[7231:1012058] complete ----1-----
2018-09-26 16:56:11.540705+0800 GCDDemo[7231:1012057] ----3-----
2018-09-26 16:56:12.541442+0800 GCDDemo[7231:1012057] complete ----3-----
信号量的初始值设置为3时输出结果:
2018-09-26 16:56:43.973919+0800 GCDDemo[7248:1014760] ----1-----
2018-09-26 16:56:43.973922+0800 GCDDemo[7248:1014763] ----2-----
2018-09-26 16:56:43.973923+0800 GCDDemo[7248:1014761] ----3-----
2018-09-26 16:56:44.974419+0800 GCDDemo[7248:1014763] complete ----2-----
2018-09-26 16:56:44.974419+0800 GCDDemo[7248:1014761] complete ----3-----
2018-09-26 16:56:44.974451+0800 GCDDemo[7248:1014760] complete ----1-----
从打印中可以总结如下:
- 信号量的初始值设置的数量,就是最多能有几个线程运行的数量,当然了初始值为
0
,也可以使用dispatch_semaphore_signal
使信号量加1
。
注意点:
dispatch_semaphore_wait
和dispatch_semaphore_signal
要成对
出现,否则程序会崩溃。
GCD的常见面试题
一.输出题(自行检测)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
while (1) {
};
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("come.test.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
while (1) {
};
}
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("come.test.serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
二.解答题
1、 dispatch_group_async
是否一定能实现线程同步?
2、dispatch_barrier_sync
和dispatch_barrier_async
的区别和相同点有哪些?
3、dispatch_group_async
和dispatch_group_enter,dispatch_group_leave
实现线程同步有什么区别?
实际使用中的一些例子
写在后面的话
有问题的地方,请多指教,这个主要供自己复习和学习使用,温故知新~