最近再次看了一下GCD,之前也只是停留在简单使用一下其中函数的程度,现在多了一点理解,做个归纳。
其实使用GCD的函数,很容易注意到有一个词是经常出现的,就是:Dispatch。查了下,是派遣、分派的意思,我目前对于GCD的理解就是基于这个词。虽然它是多线程编程的一个方式,但是不需要我们直接的管理、操纵线程,而是通过把任务(方法、代码块等形式)给定到特定的队列(queue),然后这些队列会把这些任务派遣给各个线程,就好像有几个非常给力的办事员,只要把任务给他们,他们会把任务分配好、给到正确的线程里。我觉得核心的东西就是这些队列对于任务分配、派遣,而这个就是Dispatch的意思。相对于直接管理线程,GCD就是多了一些queue,而这些queue就是分派任务的,所以我认为这些队列和它们分派任务这个动作是GCD的关键之处(目前就领会到这个层次了……)。
所以就有两个部分需要理解:1、有哪些队列(queue)以及它们的性质 2、各种函数,通过它们可以在同一个队列中执行不同的操作,其实这就是影响了队列分派(dispatch)的一步。 总结说,就是选择什么队列、执行什么样的操作的问题。
1、队列:
队列的类型名为dispatch_queue_t,首先可分为系统自带和自定义两类。系统自带的有4个队列,首先是主队列,使用
dispatch_get_main_queue()
得到,然后是3个global queue,使用
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0)
得到,其中两个参数第一个是制定优先级,3个队列分别有高、中、下3种优先级,使用DEFAULT 的话,就是中,第二个参数暂不清楚。
自定义队列的构建通过:
dispatch_queue_t queue =
dispatch_queue_create("first",
DISPATCH_QUEUE_SERIAL);
第一个参数是使用一个字符串来标识这个队列,第二个参数是选择这个队列为串行还是并行。在串行队列里,一个任务必须执行完之后才会返回,即使使用的是dispatch_async 这个一步调用的方法;但是在并行队列里,使用dispatch_async 这样的异步方法是会立即返回的,程序会继续向下执行不会阻塞,而通过其他函数如dispatch_sync也可以实现类似 串行队列的效果,即必须这个代码块执行完才会继续向下执行。
2、关于各种函数:
GCD里面的函数几乎都是具有函数指针和block两种形式,函数名上面的区别就是使用函数指针的后缀多了一个_f,其实由此也可看出函数指针和block在功能上的相似了。
(1)首先最常用的就是:如dispatch_async了,实现异步调用,上段代码:
- (void)disptch_sync_test{ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ sleep(2); //线程沉睡2秒模拟该操作执行了一段时间 NSLog(@"串行"); }); dispatch_async(queue, ^{ sleep(2); NSLog(@"并行"); }); NSLog(@"结束"); }
执行结果是:
2014-10-12 14:32:12.047 GCD_Demo[763:41657] 结束 2014-10-12 14:32:14.048 GCD_Demo[763:41689] 并行 2014-10-12 14:32:14.048 GCD_Demo[763:41687] 串行
“结束”的输出在最前面,说明前面两个block里面的代码还没有执行结束,就运行到NSLog(@"结束")了。
和它相似的是dispatch_sync ,它会阻塞线程,当它执行完了才会让程序继续向下,例如将上例中的第一个函数修改下变为:
- (void)disptch_sync_test{ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_sync(queue, ^{ //改了这里,少了个a sleep(2); NSLog(@"串行"); }); dispatch_async(queue, ^{ sleep(2); NSLog(@"并行"); }); NSLog(@"结束"); }
运行结果就变为:
2014-10-12 14:37:14.928 GCD_Demo[825:44430] 串行 2014-10-12 14:37:14.929 GCD_Demo[825:44430] 结束 2014-10-12 14:37:16.930 GCD_Demo[825:44466] 并行
“串行”的输出在最前面,表明第一个block的代码必须执行完才返回,才会继续向下,而因为第二个函数没有修改,依然是异步调用,所以“结束”的输出又会在“并行”的前面。
(2)有时会有这种情况,比如有一个类的对象S的构建需要两个不同的参数,假设为A、B,它们各自都需要一段操作来获取,但是A和B之间是互不关联的,所以肯定会使用异步并发的去分别获取A和B,就可以同时的获取A、B,代码可能这些写:
- (void)disptch_sync_test{ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ sleep(2); NSLog(@"获取A"); //这里代表执行操作获取A //(11) }); dispatch_async(queue, ^{ sleep(4); NSLog(@"获取B"); //这里代表操作获取B //(22) }); NSLog(@"使用A和B构建S"); //这里代表操作构建S }
但实际是构建S的时候,A,B还在执行中,所以不行。那把构建S放到(11)位置一边跟着获取A执行,但是B又得不到,放到(22)位置去一样道理,那怎么办?比如:
- (void)disptch_sync_test{ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); __block BOOL finish_A = NO,finish_B = NO; //使用__block修饰,以便在block内部修改值 dispatch_async(queue, ^{ sleep(2); NSLog(@"获取A"); //这里代表执行操作获取A finish_A = YES; if (finish_A && finish_B) { [self constructS]; } //(11) }); dispatch_async(queue, ^{ sleep(4); NSLog(@"获取B"); //这里代表操作获取B finish_B = YES; if (finish_A && finish_B) { [self constructS]; } //(22) }); //NSLog(@"使用A和B构建S"); //这里代表操作构建S } -(void )constructS{ NSLog(@"使用A和B构建S"); //这里代表操作构建S }
但是这样的操作台麻烦了,需要额外定义一些变量和方法,如果使用Dispatch
Group就可以很好解决了。例子如下:
- (void)disptch_sync_test{ 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, ^{ sleep(2); NSLog(@"获取A"); //这里代表执行操作获取A }); dispatch_group_async(group, queue, ^{ sleep(4); NSLog(@"获取B"); //这里代表操作获取B }); dispatch_group_notify(group, queue, ^{ NSLog(@"使用A和B构建S"); //这里代表操作构建S }); }
我的理解是这里使用一个group把多个操作进行了一定程度的绑定,然后对于使用dispatch_group_async 调用的任务是异步执行,不需等它执行完,这样就同时获取A、B,但是使用dispatch_group_notify 执行的代码会等到dispatch_group_async 执行完才能执行,所以可以很好的解决上述情况下的需求,相对很简洁、一目了然。
(3)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), queue, ^{ NSLog(@"XXX"); });
可以指定在多少时间之后执行,相对NSTimer更简洁,如果你不是想做重复性的操作的话。貌似时间的精度相对NSTimer要好,因为见过解析视频帧的源码里是使用这个方法来确定下一帧的时间的,具体精度如何不清楚。
(4)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(10, queue, ^(size_t idx) { sleep(2); NSLog(@"XXXX %zu",idx); //传入的参数是调用的次数序号 });
可以用来多次的执行block里面的语句块。从输出可以看出,应该是采用了递归,具体怎么递归的,没看太懂。
(5)
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"XXXX"); });
使用它可以保证block里面的代码在程序的生命周期里只会执行一次,所以经常用来构建单例。