GCD
1.简介
1.1 GCD官方解释
Grand Central Dispatch(GCD)是异步执行任务的技术之一,一般将应用程序中记述的线程管理用的代码再系统级中实现。开发者只需要定义想执行的任务并追加到适当的 Dispatch Queue 中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样就比以前的线程更有效率。
1.2 GCD的好处
- GCD可用于多核的并行运算
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
2.任务和队列
学习GCD之前,我们要了解GCD中两个核心概念:任务和队列。
2.1 任务
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是是否具备开启新线程的能力。
- 同步执行:只能在当前线程中执行任务,不具备开启新线程的能力。
- 异步执行:可以在新的线程中执行任务,具备开启新线程的能力。
2.2 队列
队列:这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有两种队列:串行队列和并行队列。
- 并行队列:可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务),并行功能只有在异步函数下才有效。
- 串行队列:让任务一个接着一个的执行(一个任务执行完毕后,再执行下一个任务)
3.GCD的使用步骤
GCD的使用步骤其实很简单,只有两步。
1.创建一个队列(串行队列或并行队列)
2.将任务添加到队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)
3.1 队列的创建方法
可以使用 dispatch_queue_create 来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于Debug,可为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并行队列。
// 串行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL); // 并行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
在下图中,我们可以看到,唯一标识符的作用,在Debug时,更容易确定是哪个线程除了问题。
对于并行队列,还可以使用 dispatch_get_global_queue 来创建全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT,第二个参数暂时没用,用0即可。
3.2 任务的创建方法
// 同步执行任务创建方法 dispatch_sync(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码 }); // 异步执行任务创建方法 dispatch_async(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码 });
虽然使用GCD只需两步,但是既然我们有两种队列,两种任务执行方式,那么我们就有了四种不同的组合方式,这4种不同的组合方式是:
- 并行队列 + 同步执行
- 并行队列 + 异步执行
- 串行队列 + 同步执行
- 串行队列 + 异步执行
实际上,我们还有一种特殊队列是主队列,那样就有6种不同的组合方式:
- 主队列 + 同步执行
- 主队列 + 异步执行
那么这几种不同的组合方式有什么区别呢,看图:
并行队列 | 串行队列 | 主队列 | |
同步(sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 |
异步(async) | 有开启新线程,并行执行任务 | 有开启新线程(1条),串行执行任务 | 没有开启新线程,串行执行任务 |
4. GCD的基本使用
4.1 并行队列 + 同步执行
不会开启新线程,执行完一个任务,再执行下一个任务
- (void)lesson2{ // 创建一个并行队列 NSLog(@"syncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } }); NSLog(@"syncConcurrent---end"); /* 我们可以看出,任务都是在主线程中执行的,由于只有一个线程,所以任务只能一个一个执行 同时,我们还可以看到,都打印在 begin 和 end之间,说明,任务添加到队列之后,马上会执行的 */ // 2017-12-06 18:30:42.338766+0800 GCD[20449:496505] syncConcurrent---begin // 2017-12-06 18:30:42.339001+0800 GCD[20449:496505] 1 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339187+0800 GCD[20449:496505] 1 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339325+0800 GCD[20449:496505] 1 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339448+0800 GCD[20449:496505] 2 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339603+0800 GCD[20449:496505] 2 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339763+0800 GCD[20449:496505] 2 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.339919+0800 GCD[20449:496505] 3 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.340269+0800 GCD[20449:496505] 3 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.340406+0800 GCD[20449:496505] 3 - - - - <NSThread: 0x600000065640>{number = 1, name = main} // 2017-12-06 18:30:42.340537+0800 GCD[20449:496505] syncConcurrent---end }
4.2 并行队列 + 异步执行
可同时开启多线程,任务交替执行
- (void)lesson3{ // 创建一个并行队列 NSLog(@"syncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("并行队列", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } }); NSLog(@"syncConcurrent---end");
/*
我们可以看出,除了主线程,又开启了3个线程,并且任务是交替同时执行的,另一方面可以看出,任 务不是马上执行,而是将所有任务添加到队列之后,才开始异步执行。
*/ // 2017-12-06 22:16:40.036449+0800 GCD[1001:46014] syncConcurrent---begin // 2017-12-06 22:16:40.036735+0800 GCD[1001:46014] syncConcurrent---end // 2017-12-06 22:16:40.036914+0800 GCD[1001:46328] 2 - - - - <NSThread: 0x6000004648c0>{number = 4, name = (null)} // 2017-12-06 22:16:40.036912+0800 GCD[1001:46325] 3 - - - - <NSThread: 0x60400027d380>{number = 5, name = (null)} // 2017-12-06 22:16:40.036956+0800 GCD[1001:46330] 1 - - - - <NSThread: 0x60400027d440>{number = 3, name = (null)} // 2017-12-06 22:16:40.037143+0800 GCD[1001:46328] 2 - - - - <NSThread: 0x6000004648c0>{number = 4, name = (null)} // 2017-12-06 22:16:40.037306+0800 GCD[1001:46330] 1 - - - - <NSThread: 0x60400027d440>{number = 3, name = (null)} // 2017-12-06 22:16:40.037337+0800 GCD[1001:46325] 3 - - - - <NSThread: 0x60400027d380>{number = 5, name = (null)} // 2017-12-06 22:16:40.037505+0800 GCD[1001:46328] 2 - - - - <NSThread: 0x6000004648c0>{number = 4, name = (null)} // 2017-12-06 22:16:40.037608+0800 GCD[1001:46330] 1 - - - - <NSThread: 0x60400027d440>{number = 3, name = (null)} // 2017-12-06 22:16:40.039677+0800 GCD[1001:46325] 3 - - - - <NSThread: 0x60400027d380>{number = 5, name = (null)} }
4.3 串行队列 + 同步执行
不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行另一个任务
- (void)lesson4{ NSLog(@"syncConcurrent - begin"); dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } }); NSLog(@"syncConcurrent - end");
/* 我们可以看到,所有的任务都是在主线程中执行的,并没有开启新的线程,而且由于是串行队列,所以按顺序一个一个执行,而且任务都是添加到队列中马上执行的。
*/ // 2017-12-06 22:23:10.808603+0800 GCD[1066:51102] syncConcurrent - begin // 2017-12-06 22:23:10.809058+0800 GCD[1066:51102] 1 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809336+0800 GCD[1066:51102] 1 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809539+0800 GCD[1066:51102] 1 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809706+0800 GCD[1066:51102] 2 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.809884+0800 GCD[1066:51102] 2 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810055+0800 GCD[1066:51102] 2 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810226+0800 GCD[1066:51102] 3 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810494+0800 GCD[1066:51102] 3 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810713+0800 GCD[1066:51102] 3 - - - - <NSThread: 0x604000079ec0>{number = 1, name = main} // 2017-12-06 22:23:10.810813+0800 GCD[1066:51102] syncConcurrent - end }
4.4 串行队列 + 异步执行
会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
- (void)lesson5{ NSLog(@"syncConcurrent - begin"); dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } });
/* 我们可以看到,开启了一条新线程,但是任务还是串行,所以一个一个执行,任务不是马上执行,而是将所有任务添加到队列后才开始同步执行。*/ NSLog(@"syncConcurrent - end"); // 2017-12-06 22:25:04.040810+0800 GCD[1082:52532] syncConcurrent - begin // 2017-12-06 22:25:04.041050+0800 GCD[1082:52532] syncConcurrent - end // 2017-12-06 22:25:04.041171+0800 GCD[1082:52765] 1 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.041814+0800 GCD[1082:52765] 1 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.042084+0800 GCD[1082:52765] 1 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.043690+0800 GCD[1082:52765] 2 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.043849+0800 GCD[1082:52765] 2 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.044082+0800 GCD[1082:52765] 2 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.044733+0800 GCD[1082:52765] 3 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.045220+0800 GCD[1082:52765] 3 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} // 2017-12-06 22:25:04.045922+0800 GCD[1082:52765] 3 - - - - <NSThread: 0x6040004607c0>{number = 3, name = (null)} }
主队列:GCD自带的一种特殊的串行队列,所有放在主队列中的任务,都会放到主线程中执行,可以通过dispatch_get_main_queue() 获得主队列。
4.5 主队列 + 同步执行
互等卡住不可行(在主线程中调用)
- (void)lesson6{ NSLog(@"主线程 + 同步执行 - begin"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_sync(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } });
/*因为,我们在主线程中执行这段代码。我们把任务放到了主队列中,也就是放到了主线程的队列中。而同步执行有个特点,就是对于任务是立马执行的。那么当我们把第一个任务放进主队列中,它就会立马执行,但是主线程正在处理 lesson6 的方法,所以任务1需要等待 lesson6 执行完成才能执行,当lesson6执行到任务1的使用,又需要等任务1执行完成才能执行任务2和任务3,
那么,现在的情况就是 lesson6 方法和任务1都在等对方执行完毕。这样大家互相等待,所以就卡住了。*/
因为,此时主线程正在执行 方法lesson6, 但是由于是同步执行, 当将任务1添加到队列中时会马上执行,但是由于主线程正在执行方法lesson6,任务1需要等待lesson6执行完成,当lesson6执行到任务1的时候,也需要任务1执行完成才能继续执行,所以相互等待。
NSLog(@"主线程 + 同步执行 - end"); // 2017-12-06 22:31:04.600063+0800 GCD[1120:56056] 主线程 + 同步执行 - begin // (lldb) }
那么,如果不在主线程中调用,而在其它线程中调用会如何呢?
不会开启新线程,执行完一个任务,在执行下一个任务(在其它线程中调用)
- (void)lesson7{ dispatch_queue_t queue = dispatch_queue_create("111", NULL); dispatch_async(queue, ^{ [self lesson6]; }); /*所有任务都是在主线程中执行的,并没有开启新的线程,而且由于主队列是串行队列,所以按顺序一个一个执行,并且任务添加到队列中就会马上执行*/ // 2017-12-06 22:35:25.873497+0800 GCD[1140:58833] 主线程 + 同步执行 - begin // 2017-12-06 22:35:25.878078+0800 GCD[1140:58563] 1 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.878277+0800 GCD[1140:58563] 1 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.878440+0800 GCD[1140:58563] 1 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.879158+0800 GCD[1140:58563] 2 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.879723+0800 GCD[1140:58563] 2 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.880138+0800 GCD[1140:58563] 2 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.880857+0800 GCD[1140:58563] 3 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.881020+0800 GCD[1140:58563] 3 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.881485+0800 GCD[1140:58563] 3 - - - - <NSThread: 0x600000066b80>{number = 1, name = main} // 2017-12-06 22:35:25.881695+0800 GCD[1140:58833] 主线程 + 同步执行 - end }
4.6 主队列 + 异步执行
只有在主线程中执行任务,执行完一个任务,再执行下一个任务
- (void)lesson8{ NSLog(@"主线程 + 异步执行 - begin"); dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 1 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 2 - - - - %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@" 3 - - - - %@",[NSThread currentThread]); } }); /*我们发现所有任务都是在主线程中,虽然是异步执行,具备开启线程的能力,但因为是主队列,所以所有任务都在主线程中,并一个一个执行,并且任务不是马上执行,而是将所有任务添加到队列之后,才开始同步执行。
*/ NSLog(@"主线程 + 异步执行 - end"); // 2017-12-06 22:40:01.452127+0800 GCD[1176:61800] 主线程 + 异步执行 - begin // 2017-12-06 22:40:01.452394+0800 GCD[1176:61800] 主线程 + 异步执行 - end // 2017-12-06 22:40:01.457218+0800 GCD[1176:61800] 1 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.457404+0800 GCD[1176:61800] 1 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.457966+0800 GCD[1176:61800] 1 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458234+0800 GCD[1176:61800] 2 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458409+0800 GCD[1176:61800] 2 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458602+0800 GCD[1176:61800] 2 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458829+0800 GCD[1176:61800] 3 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.458990+0800 GCD[1176:61800] 3 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} // 2017-12-06 22:40:01.459240+0800 GCD[1176:61800] 3 - - - - <NSThread: 0x60000006d940>{number = 1, name = main} }
5.GCD线程之间的通讯
在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其它线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ for (int i = 0; i < 2; ++i) { NSLog(@"1------%@",[NSThread currentThread]); } // 回到主线程 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"2-------%@",[NSThread currentThread]); }); });
6.GCD其它的方法
6.1 dispatch_after
如果想在指定时间后执行处理的情况,可以使用 dispatch_after 方法来实现。
在3秒后将指定的Block追加到 Main Dispatch Queue 中的源代码如下:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"等待至少三秒钟"); });
这里需要注意的是,dispatch_after 并不是在指定时间后执行处理,而只是在指定时间追加处理到 Dispatch Queue,此源代码与在3秒后用dispatch_async函数追加 Block 到 Main Dispatch Queue 的相同。
因为 Main Dispatch Queue 在主线程的 RunLoop 中执行,所以在比如每隔 1/60 秒执行的 RunLoop 中,Block 最快在 3 秒后执行,最慢在 3 秒 + 1/60 秒后执行,并且在 Main Dispatch Queue 有大量处理追加或主线程的处理本身有延迟时,这个时间会延长。
虽然在有严格时间的要求下使用时会出现问题,但是想在大致延迟执行处理时,这个方法很好用。
第一个参数经常使用的值是 DISPATCH_TIME_NOW ,这表示现在的时间。
第二个参数是指定延后的时间
第三个参数是指定要追加处理的 Dispatch Queue
第四个参数是要执行处理的Block
6.2 dispatch Group
在追加到 dispatch queue 中的多个处理全部结束后想执行结束处理,这种情况经常出现,只使用一个 串行队列时,只要将想执行的处理全部加到该串行队列,并在最后追加结束处理,就可以实现,但是在使用 并行队列,或同时使用多个 dispatch queue 时,就需要用到 dispatch group。
例如,追加3个Block 到 Global Dispatch Queue,这些 Block 如果全部执行完毕,就会执行 Main Dispatch Queue 中结束处理用的 Block。
- (void)viewDidLoad { [super viewDidLoad]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ for (NSInteger i = 0; i < 3; i++) { sleep(1); NSLog(@"async1 ------ %ld ----- %@",i,[NSThread currentThread]); } }); dispatch_group_async(group, queue, ^{ for (NSInteger i = 0; i < 3; i++) { sleep(1); NSLog(@"async2 ------ %ld ----- %@",i,[NSThread currentThread]); } }); dispatch_group_async(group, queue, ^{ for (NSInteger i = 0; i < 3; i++) { sleep(1); NSLog(@"async3 ------ %ld ----- %@",i,[NSThread currentThread]); } }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"终于到我了 --- %@",[NSThread currentThread]); }); }
输出结果:
2017-12-13 18:04:07.948776+0800 GCD[16160:439266] async1 ------ 0 ----- <NSThread: 0x600000278a00>{number = 3, name = (null)} 2017-12-13 18:04:07.948781+0800 GCD[16160:439267] async3 ------ 0 ----- <NSThread: 0x60400046dac0>{number = 5, name = (null)} 2017-12-13 18:04:07.948816+0800 GCD[16160:439268] async2 ------ 0 ----- <NSThread: 0x60400046d9c0>{number = 4, name = (null)} 2017-12-13 18:04:08.949200+0800 GCD[16160:439267] async3 ------ 1 ----- <NSThread: 0x60400046dac0>{number = 5, name = (null)} 2017-12-13 18:04:08.949217+0800 GCD[16160:439266] async1 ------ 1 ----- <NSThread: 0x600000278a00>{number = 3, name = (null)} 2017-12-13 18:04:08.949224+0800 GCD[16160:439268] async2 ------ 1 ----- <NSThread: 0x60400046d9c0>{number = 4, name = (null)} 2017-12-13 18:04:09.949591+0800 GCD[16160:439267] async3 ------ 2 ----- <NSThread: 0x60400046dac0>{number = 5, name = (null)} 2017-12-13 18:04:09.949769+0800 GCD[16160:439266] async1 ------ 2 ----- <NSThread: 0x600000278a00>{number = 3, name = (null)} 2017-12-13 18:04:09.955017+0800 GCD[16160:439268] async2 ------ 2 ----- <NSThread: 0x60400046d9c0>{number = 4, name = (null)} 2017-12-13 18:04:09.955370+0800 GCD[16160:438995] 终于到我了 --- <NSThread: 0x6000002613c0>{number = 1, name = main}
我们可以看到,当三个追加Block的任务执行完毕后,主线程的任务才执行,无论向什么样的 dispatch queue 中追加处理,使用 dispatch group 都可以监视这些处理执行的结束。一旦检测到所有处理执行结束,就可将结束的处理追加到 dispatch queue 中,这就是使用 dispatch group 的原因。
6.3 dispatch_semaphore (信号量)
在这里我们可以先考虑一个问题:假设现在系统有两个空闲资源可以使用,但同一时间要有三个线程需要访问,这种情况下,该怎么处理呢?或者,我们要下载很多图片,并发异步执行,但是我们又担心太多线程cpu吃不消,那么我们就可以使用信号量来控制一下线程最大开辟数。
信号量:就是一种可用来控制访问资源的数量的标识。设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统可以按照我们们指定的信号量数量来执行多个线程。有点类似于锁的机制。
主要有三个函数:
//创建信号量,参数:信号量的初值,如果小于0则会返回NULL dispatch_semaphore_create(信号量值) //等待信号量(减少信号量) dispatch_semaphore_wait(信号量,等待时间) //发送信号量 (增加信号量) dispatch_semaphore_signal(信号量) 一般都是先 降低,然后再提高
一切都在代码中:
// creat 的 value 表示,最多有几个资源可以访问 dispatch_semaphore_t semaphore = dispatch_semaphore_create(2); // 获得全局队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 任务1 dispatch_async(queue, ^{ // 如果没有就无限制的等待 long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"任务1 result - %ld",result); NSLog(@"开始执行任务1"); sleep(4); NSLog(@"结束执行任务1"); dispatch_semaphore_signal(semaphore); }); // 任务2 dispatch_async(queue, ^{ // 如果没有就无限制的等待 long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"任务2 result - %ld",result); NSLog(@"开始执行任务2"); sleep(4); NSLog(@"结束执行任务2"); dispatch_semaphore_signal(semaphore); }); // 任务3 dispatch_async(queue, ^{ // 如果没有就无限制的等待 long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"任务3 result - %ld",result); NSLog(@"开始执行任务3"); sleep(3); NSLog(@"结束执行任务3"); dispatch_semaphore_signal(semaphore); });
执行完的代码:
2018-02-01 14:33:45.866447+0800 定时器[1465:121647] 任务2 result - 0 2018-02-01 14:33:45.866480+0800 定时器[1465:121648] 任务1 result - 0 2018-02-01 14:33:45.867231+0800 定时器[1465:121647] 开始执行任务2 2018-02-01 14:33:45.867241+0800 定时器[1465:121648] 开始执行任务1 2018-02-01 14:33:47.869069+0800 定时器[1465:121648] 结束执行任务1 2018-02-01 14:33:47.869292+0800 定时器[1465:121645] 任务3 result - 0 2018-02-01 14:33:47.869420+0800 定时器[1465:121645] 开始执行任务3 2018-02-01 14:33:49.869980+0800 定时器[1465:121647] 结束执行任务2 2018-02-01 14:33:50.870619+0800 定时器[1465:121645] 结束执行任务3
我们可以看出来,因为只有2个资源,所以当任务1任务2占了两个资源之后,任务3就只能一直等待,直到任务1和任务2之间有一个任务完成之后,才能往下执行,我们设置的是无限制等待,如果我们设置任务3的等待时间为1的话,且任务1和任务2的执行时间超过1,那么 wait 方法就会返回一个不为 0 的长整型。这个例子只是简单的理解一下信号量的作用。
6.4 dispatch_barrier
简单的先来理解一下,在一个并行队列中,有多个线程在执行多个任务,在这个并行队列中,有一个dispatch_barrier任务,这样会有一个什么效果呢?我们直接上代码:
// 创建一个并行队列 dispatch_queue_t queue = dispatch_queue_create(0,DISPATCH_QUEUE_CONCURRENT); // 任务1 dispatch_async(queue, ^{ for (NSInteger i = 0; i < 10; i++) { sleep(1); NSLog(@"任务1中的 - %ld",i); } NSLog(@"----------------------------"); }); // 任务2 dispatch_barrier_async(queue, ^{ for (NSInteger i = 10; i < 20; i++) { sleep(1); NSLog(@"任务2中的 - %ld",i); } NSLog(@"任务2当前线程 - %@",[NSThread currentThread]); NSLog(@"----------------------------"); }); NSLog(@"我是主线程"); // 任务3 dispatch_async(queue, ^{ for (NSInteger i = 20; i < 30; i++) { sleep(1); NSLog(@"任务3中的 - %ld",i); } });
我们可以看到,任务1先执行完,然后执行 barrier 里面的,再执行 任务3。dispatch_barrier_sync确实是会在队列中充当一个栅栏的作用,凡是在他之后进入队列的任务,总会在dispatch_barrier_sync之前的所有任务执行完毕之后才执行。见名知意,dispatch_barrier_sync是会在主线程执行队列中的任务的,所以,Running on Main Thread这句话会被阻塞,从而在barrier之后执行。也就是说 dispatch_barrier_sync 和 dispatch_barrier_async 的区别就在于,是否会阻塞主线程。
注意:这个函数仅对于自己创建的并行队列有效。如果在全局队列中,因为每一次使用,系统都可能给你分配不同的并行队列,所以也就变得没有意义,串行队列也就不用说了。
6.5 GCD 精准的定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); // 第二个参数是间隔时间,第四个参数是精确度,0表示没有误差 dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC); dispatch_source_set_event_handler(timer, ^{ NSLog(@"123123123"); }); dispatch_resume(timer);