笔者是 iOS 开发的新手, 最近学习了一下 GCD, 怕忘了记录一下.
先介绍一下 GCD, Grand Central Dispatch (GCD)是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。当然, 这些都是百度的.
要想明白 GCD 的原理,先要了解下面这几个概念: 同步, 异步, 串行, 并行, 队列, 这几个概念百度都有, 说的都很精华, 笔者也就不复制粘贴了.
了解完刚刚说的几个概念了, 接下来开始说我们的主角了.笔者认为, GCD 根本操作是把将要执行的任务, 放到队列中执行. 那么这就产生了两个动作, 任务放到队列中和执行队列中的任务.那我们就从这两个动作入手, 开始分析 GCD.
一. 将任务添加队列中
GCD 提供了两种添加任务的方式:
同步添加:dispatch_sync(dispatch_queue_tqueue,dispatch_block_tblock);
异步添加:dispatch_async(dispatch_queue_tqueue,dispatch_block_tblock);
两种方式的参数都是一样的, 第一个参数是指要添加到那个队列, 第二参数是 block 函数体. 将要执行的任务写在 block 中, 然后在将这个 block 函数体添加到队列中.
同步添加的方式会阻塞当前操作所在的线程, 等待dispatch_sync(); 方法执行结束以后, 再继续执行其他操作. 相反,异步添加的方式不会阻塞当前操作所在的线程, 无需等待dispatch_async(); 方法执行结束, 线程会继续执行其他的操作.
二. 执行队列中的任务
既然任务是添加到队列中去执行的, 不同类型的队列, 当然也会影响任务的执行方式.
定义队列: dispatch_queue_t 对象名;
串行队列: dispatch_queue_t serialQ =dispatch_queue_create(constchar*label,dispatch_queue_attr_tattr);
第一个参数: 队列的标识, 也就是队列的名字, 便于调试. 第二个参数: 如果写DISPATCH_QUEUE_SERIAL 或者 NULL, 则表示当前队列为串行队列; 如果写
DISPATCH_QUEUE_CONCURRENT, 则表示当前队列为自定义并行队列.
串行队列 block 函数体执行任务的过程中, 由队列创建出一个子线程, 所有任务都在这个一个子线程中串行执行.
并行队列: 除了刚刚自定义并行队列的方法以外,系统还提供了四种不同优先级的全局队列(与并行队列类似, 只是不同全局队列之间的执行任务优先级不同).
全局队列: dispatch_queue_t concurrentQ = dispatch_get_global_queue(longidentifier,unsignedlongflags);
第一个参数: 系统提供的四种不同的宏, 表示四种不同的执行优先级
高: DISPATCH_QUEUE_PRIORITY_HIGH
默认: DISPATCH_QUEUE_PRIORITY_DEFAULT
低: DISPATCH_QUEUE_PRIORITY_LOW
后台: DISPATCH_QUEUE_PRIORITY_BACKGROUND
第二个参数: 系统保留的参数, 不知道什么用途, 写0即可, 其他值暂时没测试过.
并行队列和全局队列 block 函数体执行任务的过程中, 由队列创建出多个子线程, 所有任务是并行执行的.
主队列:dispatch_queue_t mainQ =dispatch_get_main_queue();
所有队列的主队列, 在所有队列执行后执行,获取了主队列也相当于获取了当前操作的线程(可能是主线程, 也可能是子线程).
拓展
如果同步添加任务到主队列中执行, 可能会造成死锁. 如果当前操作在子线程中, 则不会死锁; 如果当前操作在主线程中, 则必定死锁.死锁的原因: 因为是同步添加任务, 线程会阻塞, 等待操作完成. 可是任务是同步添加到主线程的主队列中执行, 所有任务会等待主队列添加之前的任务完成后再执行自己的任务, 可是添加之前的任务却是将自己的任务添加进去并执行结束, 两个添加操作和执行操作是互相等待的, 所以也就死锁了. 代码和执行结果如下:
主线程同步主队列:
NSLog(@"同步添加: 开始添加任务");
dispatch_queue_t mainQ = dispatch_get_main_queue();
dispatch_sync(mainQ, ^{
NSLog(@"同步添加: 开始执行任务");
NSLog(@"同步添加: 结束执行任务");
});
NSLog(@"同步添加: 完成所有任务");
子线程同步主队列:
- (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(testSync) toTarget:self withObject:nil];
}
- (void)testSync
{
NSLog(@"同步添加: 开始添加任务");
dispatch_queue_t mainQ = dispatch_get_main_queue();
dispatch_sync(mainQ, ^{
NSLog(@"同步添加: 开始执行任务");
NSLog(@"同步添加: 结束执行任务");
});
NSLog(@"同步添加: 完成所有任务");
}
dispatch_group_async :
可以实现监听一组任务是否完成,完成后得到通知执行其他的操作.
dispatch_group_notify :
组所在的队列最后一个任务执行技术以后, 会通知执行其他的操作, 被通知的操作, 可以放在 notify 这个组中执行. 这个操作可以放在任意的队列中, 代表所属队列最后需要执行的任务组.
dispatch_barrier_async:
在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行. 也就是队列执行时起到中断的功能.
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, ^{
NSLog(@"group1 ");
});
dispatch_group_async(group, queue, ^{
NSLog(@"group2 ");
});
dispatch_barrier_async(queue, ^{
NSLog(@"中断");
});
dispatch_group_async(group, queue, ^{
NSLog(@"group3 ");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"最后执行");
});
执行结果如下:
dispatch_apply :
执行某个代码片段N次。
dispatch_once :
它可以保证整个应用程序生命周期中某段代码只被执行一次, 一般手动创建单例模式的时候, 会经常用到.
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
dispatch_after :
等待多久后, 执行任务.
dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_after(waitTime, dispatch_get_main_queue(), ^(void){
});
总结
- dispatch_sync();是指向队列中添加 block 函数体, 这个操作相对于当前操作所在线程的其他操作,是同步执行的. dispatch_async();与其相反, 是异步执行的.
而 block 函数体中的执行任务方式, 是由队列的特性决定的, 如果是串行队列, 就只有一个子线程工作, 如果是并行队列, 就会有多个子线程工作. - 队列和线程不是同一个概念, GCD 是对队列的管理.
- 多线程是一种编程技术, 是一个形容词, 实现多线程的方法有很多, iOS 中有 NSThread, Cocoa NSOperation, GCD, 等等...以后会研究到.
- 使用多线程这种技术编程的目的是为了实现异步.
- 同步还是异步, 对应的是单线程还是多线程. 串行还是并行, 是 CPU 本身的特性, 与 CPU 单核还是多核有关.
- 并行和并发是有区别的: 并行是两个线程执行互相不占用资源, 而并发是两个线程占用资源, 只不过 CUP 会自动进行上下文切换, 自动分配当前时刻应该执行哪个线程, 不同时刻执行的线程可能会不同.
- 单核 CUP 的并行相当于并发.
先写这么多吧, 笔者也是初学者, 肯定有不妥当的地方, 不完整的地方笔者会补上.