一.GCD简介
GCD(Grand Central Dispatch) 是iOS系统一套多线程操作API。字面意思既是宏观全局派发。相比于其他多线程API,GCD操作简单,功能强大,且会自动充分利用系统硬件资源。操作简单是因为我们无需管理线程,只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度任务,由系统直接提供线程管理。功能强大是指系统封装好了很多C函数,调用这些C函数就可以实现线程操作中的绝大多数功能。
GCD里很重要的一个概念是队列queue,队列是一种里面的元素按先进先出规则的线性结构。我们可以把任务(往往都是block)往队列里添加,系统会自动去队里里取出任务,然后根据当前的队列是并行队列 还是串行 队列,响应的去分配到线程执行。
队列相当于我们与系统交互的媒介,我们把任务往队列里扔,系统去队列里取出任务,开辟线程,分配任务。队列有并行队列与串行队列之分,不同队列有不同表现形式。
二.并行串行、同步异步介绍
学习GCD,首先要明白两组概念,并行和串行、同步与异步
并行和串行比较好理解,按字面意思,并行指的并发执行,也就是开辟多个线程同时去执行队列里任务,串行的表现形式就是添加到队列里的任务会同时执行,因此后添加进队列的任务可能会先完成,而先添加进队列的任务不一定就先完成,串行则是指队里中的任务严格按照添加进队列的顺序先后执行。
同步异步主要是指是否阻塞当前代码执行, 等待队列里的任务都执行完才可以继续执行。同步执行会先执行队列里的任务,执行完后等待dispatch_sync函数返回才能继续执行下面操作,异步执行不需要等待队列任务执行完成,将任务添加到队列后,dispatch_sync函数马上返回,继续执行下面操作,同时另外开辟新线程去执行队列里的任务。同步执行时候 系统会尽量做到不开辟新线程,只在当前线程里执行队列中的任务。
三.GCD使用
使用GCD很简单,两步即可完成,
1.首先创建队列,
2.然后将任务放到队列里。
1.创建队列
创建队列 可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于唯一标志队列,可以为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并行队列。默认NULL表示串行队列。
// 串行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行队列的创建方法 dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT
系统为了方便开发使用,已经定义好了一个全局并发队列,dispatch_get_global_queue,创建时候有两个参数,第一个参数表示队列优先级,一般用默认优先级DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数由系统预留做以后使用,现在没用,可以先用0表示。
2.向队列里分配任务,任务以block的形式被添加到队列中
使用同步或者异步两种方式分配任务
// 同步执行任务创建方法 dispatch_sync(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 所需要执行的任务 });
// 异步执行任务创建方法 dispatch_async(queue, ^{ NSLog(@"%@",[NSThread currentThread]); // 所需要执行的任务 });
其中,任务既可以是临时声明的block,也可以是提前定义好的block。
这个block是dispatch_async和dispatch_sync的参数
@interface ViewController ()
@property (nonatomic, copy) dispatch_block_t block;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//定义队列里需要执行的block,即任务
_block = ^(){
for (int i = 0; i < 10 ; i++) {
NSLog(@"%d-------%@",i,[NSThread currentThread]);
}
};
//创建队列 并行队列
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
//异步执行创建好的队列
dispatch_async(queue,_block);
NSLog(@"等不等我");
}
运行结果如下
2017-09-13 00:12:13.995 GCD[1148:97167] 等不等我
2017-09-13 00:12:13.995 GCD[1148:97384] 0-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.997 GCD[1148:97384] 1-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.998 GCD[1148:97384] 2-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.998 GCD[1148:97384] 3-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.999 GCD[1148:97384] 4-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.999 GCD[1148:97384] 5-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:13.999 GCD[1148:97384] 6-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:14.000 GCD[1148:97384] 7-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:14.000 GCD[1148:97384] 8-------<NSThread: 0x600000265400>{number = 3, name = (null)}
2017-09-13 00:12:14.003 GCD[1148:97384] 9-------<NSThread: 0x600000265400>{number = 3, name = (null)}
可以看到使用符合dispatch_block_t 形式的block都可以往队列里去添加,而dispatch_block_t的类型如下图所示
可以看到,它就是一个参数与返回值都为空的block。
3.四种情况
两种队列和两种执行方式组合起来有四种效果,分别是同步并行 同步串行 异步并行 和异步串行,
四种方式分别有不同的效果。在这写条件里,执行方式起主导作用,也就是同步和异步执行起决定因素。
1)同步并行
- (IBAction)syncconcurrence:(id)sender {
NSLog(@"开始");
//创建并行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//向队列中添加任务一
dispatch_sync(queue, _block);
//向队列中添加任务二
dispatch_sync(queue, _block);
//向队列中添加任务三
dispatch_sync(queue, _block);
NSLog(@"等不等我");
}
运行效果如下
2017-09-13 00:25:32.730 GCD[1181:107620] 开始
2017-09-13 00:25:32.731 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.731 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.731 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.731 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.732 GCD[1181:107620] 0-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.732 GCD[1181:107620] 1-------<NSThread: 0x60000007b980>{number = 1, name = main}
2017-09-13 00:25:32.733 GCD[1181:107620] 等不等我
可以看到结果是按添加到队列里的顺序,依次在主线程执行。
同步意味着阻塞,等队列里的任务执行完 才能继续往下执行NSLog(@"等不等我")这句代码,并且不会开辟新线程,尽量在原有线程里执行队列里的任务,虽然队列是并发队列,队列想要系统去开辟多个线程去执行,但是前面的执行方式已经限定,系统不能开辟新线程,只能在原有线程里面去执行队列里的任务,结果跟单线程一样,所以最终结果是依次执行队列里的任务。
2)同步串行
//同步串行
- (IBAction)syncseria:(id)sender {
NSLog(@"开始");
//创建并行队列 这里使用NULL缺省表示创建串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
//向队列中添加任务一
dispatch_sync(queue, _block);
//向队列中添加任务二
dispatch_sync(queue, _block);
//向队列中添加任务三
dispatch_sync(queue, _block);
NSLog(@"等不等我");
}
运行结果如下
2017-09-13 00:29:42.657 GCD[1196:110692] 开始
2017-09-13 00:29:42.657 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.657 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.658 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.659 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.659 GCD[1196:110692] 0-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.660 GCD[1196:110692] 1-------<NSThread: 0x60800007f180>{number = 1, name = main}
2017-09-13 00:29:42.660 GCD[1196:110692] 等不等我
同步的意义同上,串行意味着按添加到队列里的顺序去执行队列里的任务,所以该组合效果与上面的效果是相同的。
3)异步并行
//异步并行
- (IBAction)asyncconcurrence:(id)sender {
NSLog(@"开始");
//创建并行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
//向队列中添加任务一
dispatch_async(queue, _block);
//向队列中添加任务二
dispatch_async(queue, _block);
//向队列中添加任务三
dispatch_async(queue, _block);
NSLog(@"等不等我");
}
2017-09-13 00:53:26.554 GCD[1251:127458] 开始
2017-09-13 00:53:26.554 GCD[1251:127458] 等不等我
2017-09-13 00:53:26.554 GCD[1251:128289] 0-------<NSThread: 0x60800026e6c0>{number = 4, name = (null)}
2017-09-13 00:53:26.554 GCD[1251:128296] 0-------<NSThread: 0x600000079c80>{number = 5, name = (null)}
2017-09-13 00:53:26.554 GCD[1251:128297] 0-------<NSThread: 0x60800026da00>{number = 6, name = (null)}
2017-09-13 00:53:26.557 GCD[1251:128289] 1-------<NSThread: 0x60800026e6c0>{number = 4, name = (null)}
2017-09-13 00:53:26.558 GCD[1251:128296] 1-------<NSThread: 0x600000079c80>{number = 5, name = (null)}
2017-09-13 00:53:26.559 GCD[1251:128297] 1-------<NSThread: 0x60800026da00>{number = 6, name = (null)}
异步并行执行不会阻塞当前代码执行,系统将任务都添加到队列后马上继续执行下面的代码,由系统去队列里取出任务,然后开辟数个线程(线程数量不确定,由系统决定)去执行队列里的任务。
4)异步串行
//异步串行
- (IBAction)asyncseria:(id)sender {
NSLog(@"开始");
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
//向队列中添加任务一
dispatch_async(queue, _block);
//向队列中添加任务二
dispatch_async(queue, _block);
//向队列中添加任务三
dispatch_async(queue, _block);
NSLog(@"等不等我");
}
运行效果如下
2017-09-13 00:53:44.740 GCD[1251:127458] 开始
2017-09-13 00:53:44.740 GCD[1251:127458] 等不等我
2017-09-13 00:53:44.741 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.741 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.742 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.743 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.743 GCD[1251:128663] 0-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
2017-09-13 00:53:44.743 GCD[1251:128663] 1-------<NSThread: 0x60800026cfc0>{number = 7, name = (null)}
与异步并行的相同点是都不会阻塞当前的代码执行,系统将任务都添加到队列后马上继续执行下面的代码,由系统去队里里取出任务,开辟新线程去执行队列里的任务。区别是串行执行时候,队列里的任务只能按顺序依次执行,所以系统仅仅开辟一个新线程就可以完成任务。
四种组合,在实际开发中应当如何选取?
同步方式使用较少,因为同步方式会阻塞当前线程,并且按顺序执行队列任务,这样的效果跟没有使用队列的情况下并没有什么差别,同步执行也不会开辟新线程, 使用意义不是很大。
异步使用场景较多,尤其是涉及到耗时操作,比如网络请求。在发出网络请求时候,我们肯定不能让主线程等待网络请求结果返回在继续执行,因此,这里要用异步dispatch_async请求。
串行队列
//创建并行队列
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
dispatch_async(queue, ^{
//获取开始执行任务的相对时间 注意这个单位是纳秒
uint64_t timeStrat = mach_absolute_time();
NSLog(@"%llu",timeStrat);
//模拟网络请求 2秒后完成
sleep(2);
NSLog(@"已完成任务1");
//回到主队列 也就是主线程里进行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor redColor];
});
});
dispatch_async(queue, ^{
//模拟网络请求 2秒后完成
sleep(2);
NSLog(@"已完成任务2");
//回到主队列 也就是主线程里进行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor greenColor];
});
});
dispatch_async(queue, ^{
//模拟网络请求 2秒后完成
sleep(2);
NSLog(@"已完成任务3");
//回到主队列 也就是主线程里进行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor purpleColor];
});
//获取结束任务的相对时间 注意这个单位是纳秒
uint64_t timeEnd = mach_absolute_time();
NSLog(@"%llu",timeEnd);
});
执行结果如下:
2017-09-13 15:58:25.534 GCD[3052:231602] 24762306847072
2017-09-13 15:58:27.536 GCD[3052:231602] 已完成任务1
2017-09-13 15:58:29.537 GCD[3052:231602] 已完成任务2
2017-09-13 15:58:31.541 GCD[3052:231602] 已完成任务3
2017-09-13 15:58:31.541 GCD[3052:231602] 24768313894166
可以看到串行队列会按任务的添加顺序依次执行。每个任务会耗时2秒,完成队列所有任务要花费的时间约为6秒。
并行队列
仅需要把创建队列时候的第二个参数设置为DISPATCH_QUEUE_CONCURRENT
即可
//创建并行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//模拟网络请求 2秒后完成
sleep(2);
NSLog(@"已完成任务1");
//回到主队列 也就是主线程里进行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor redColor];
});
});
dispatch_async(queue, ^{
//模拟网络请求 2秒后完成
sleep(2);
NSLog(@"已完成任务2");
//回到主队列 也就是主线程里进行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor greenColor];
});
});
dispatch_async(queue, ^{
//模拟网络请求 2秒后完成
sleep(2);
NSLog(@"已完成任务3");
//回到主队列 也就是主线程里进行UI操作
dispatch_async(dispatch_get_main_queue(), ^{
self.view.backgroundColor = [UIColor purpleColor];
});
});
执行效果如下
2017-09-13 15:51:43.429 GCD[3022:227333] 已完成任务2
2017-09-13 15:51:43.429 GCD[3022:227326] 已完成任务1
2017-09-13 15:51:43.429 GCD[3022:227332] 已完成任务3
可以看到并行不一定会按任务添加顺序执行,完全由系统取出任务,分配到不同线程去执行,所以先后顺序也会不同。