您的位置:首页 > 其它

GCD大白话

2015-08-25 21:29 435 查看
在硬件多核的大环境下,软件和代码也需要做出相对应的适应,才能高效地利用硬件的多核心。苹果为开发者提供了三种多进程的解决方案:NSThread、NSOperation和GCD。NSThread渐渐地淡出了,相比后两者,它更轻量级,但需要开发者手动去管理线程的生命周期,另外线程同步时的加锁也会带来一定的资源消耗。大家可以不用过多掌握。NSOperation基于GCD实现,比GCD更灵活,功能更强大。但是本文只介绍GCD。文章将分为两大部分:
一、GCD概念
首先,什么是GCD,GCD全称Grand center Dispatch,它其实只是Apple的一个库libdispatch的一个别名,作用是为并发代码在多核心硬件上的高效运行提供支持。

GCD一般和代码块Block结合使用,GCD通过提供调度队列的方式来处理开发人员交给它的代码块。调度队列(Dispatch Queues)分为两种:串行队列(Serial Queues)和并行队列( ConcurrentQueues),串行队列,顾名思义,队列中的任务一个接一个的被执行,后面的任务只有等待前面的任务执行结束才能执行,同一时刻只有单一的任务在执行,而并行队列是指队列中的多个任务可以同时被执行。值得注意的是,无论是串行队列还是并行队列,任务都是严格按照FIFO顺序被调度执行,GCD控制任务被执行的时机,而区别在于,GCD能够保证串行队列一次执行一个任务,并且按照添加到队列的顺序来执行,完成时的任务顺序和任务被调度的顺序一致;那么并行队列呢?GCD唯一能够保证的仅仅只是按照任务添加到队列的顺序来调度执行他们,但最后结束时的任务顺序如何完全取决于GCD,也就是说任务可以以任何顺序来完成,开发人员不知道何时开始一个新任务,也不知道某个时刻到底有多少个代码块在运行。简单举个栗子:假设并行队列中先后添加了四个任务:分别是任务A、任务B、任务C和任务D,那么任务B肯定在任务A被调度后才会被调度,但是B何时被调度是不知道的,由GCD决定,同时任务B可能在任务A结束之前就已经结束了。任务C一定先于任务D执行,考虑一种情况:任务C和任务D出现了时间重叠部分,那么GCD如何安排任务D呢?如果硬件上有一个可用的核心,那么GCD可能就会安排任务D运行在这个核心上,那么任务D将与任务C并行。如果没有可用的核心,GCD会安排任务D和任务C在同一个核心上运行,严格的说是并发执行,而不是并行,也就是任务C执行一段时间,立马切换上下文到任务D执行,然后再切换至任务C,因为切换时间很多,给用户的感受是“并行”。
文章一开始提到NSOperation具有更好的灵活性,其实也体现在执行顺序这一点。虽然GCD可以通过dispatch_set_target_queue更改任务的优先级,却无法更改任务在队列中的位置,也就决定了任务被调度的顺序还是固定不变,而NSOperation不但可以更改任务的优先级,还可以更改任务在队列中位置。还有一点就是NSOperation可以暂停、取消、挂起一个线程,但是GCD不可以;

Apple为开发人员提供了五种两大类队列:主线程调度队列和全局调度队列(下设四种优先级);主线程调度队列本质上来说也是一种串行队列,即队列中的任务只用一个一个地执行,区别是所有的任务都会被安排在主线程中执行。一般更新UI的操作都会放在主线程调度队列中,获取方法是dispatch_get_main_queue( )。全局调度队列有四个优先级:default、low、high、background;获取方法是dispatch_get_global_queue();Apple自身的API也会使用全局调度队列,因此开发人员添加的任务都不会是该队列中的唯一任务。
下面给出主线程更新UI的伪代码:
- (void)viewDidLoad {
[superviewDidLoad];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0), ^{
/*一些准备工作*/
dispatch_async(dispatch_get_main_queue(), ^{
/*更新UI*/
});
});
}


相比上面Apple提供的调度队列,开发人员也可以独立创建队列。
二、GCD常用方法
1、dispatch_queue_create()
该方法用于创建生成一个调度队列;

<span style="font-size:18px;">    /*
<span style="white-space:pre">	</span>第一个参数是queue名,一般采用反向DNS域名来命名
<span style="white-space:pre">	</span>第二个参数为queue类型,0或者null即默认创建串行队列
*/
dispatch_queue_t myDispatchQueue =dispatch_queue_create("com.example.hos", NULL);
dispatch_async(myDispatchQueue, ^{
NSLog(@"block");
});</span>


2、dispatch_get_main_queue() + dispatch_get_global_queue( )
用于获取调度队列;
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
/*
<span style="white-space:pre">	</span>第二个参数:Reservedfor future use. Passing any value other than zero may result in
a NULL returnvalue.
*/
dispatch_queue_t globalDispatchQueueHigh =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globalDispatchQueueLow =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globalDispatchQueueDefault =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globalDispatchQueueBackgroud =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);


3、dispatch_set_target_queue()
使用dispatch_queue_create( )生成的无论是串行还是并行队列,都是默认优先级即DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_set_target_queue( )用于变更优先级
/*
<span style="white-space:pre">	</span>将mainDispatchQueue优先级从默认优先级更改为后台优先级
*/

dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
dispatch_queue_t globalDispatchQueueBackgroud =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mainDispatchQueue,globalDispatchQueueBackgroud);


4、dispatch_after( )
用于指定时间后执行处理某任务,注意:延迟处理时间,而不是延迟添加到队列
/*
3秒后执行do something
*/

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 *NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

/*dosomething*/
});


5、dispatch_group_*
常用于等待调度队列中多个任务结束后在执行某任务时,本文以合并图片为例;
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_async(group, queue, ^{
/*下载图片1*/
});
dispatch_group_async(group, queue, ^{
/*下载图片2*/
});
dispatch_group_async(group, queue, ^{
/*下载图片3*/
});
dispatch_group_notify(group, queue, ^{
/*合并图片1,2,3*/
});

6、dispatch_barrier_async()
GCD通过dispatch barriers创建了一个读者写者锁,为读者写者问题提供了一个解决方案;
dispatch barriers是一组函数,在并发队列上工作时扮演着一个串行式的瓶颈。使用GCD的障碍函数能够确保提交的代码块是那个特定时间上唯一被执行的代码块,不会与该队列的奇特代码块同时执行,这就意味着所有先于调度障碍提交到队列的代码块都必然会在该代码块执行前就完成了。当这个代码块的执行时机到达的时候,调度障碍执行这个代码块的时间里不再执行任何其他代码块,一旦执行完成,队列就会恢复到之前的正常状态。 GCD提供了两组调度障碍函数:同步和异步。


错误:

<span style="font-size:18px;">- (void)addPhoto:(Photo *)photo {
if (photo) {
[_photoArrayaddObject:photo];
}
}

- (NSArray *)photos {

NSArray *array= [NSArray arrayWithArray:_photoArray];//此时可能有别的线程在写数据
return array;
}</span>


 改正:

<span style="font-size:18px;">   dispatch_queue_t queue =dispatch_queue_create("com.hello.hos", 0);
- (void)addPhoto:(Photo *)photo {
if (photo) {

dispatch_barrier_async(queue, ^{
[_photoArray addObject:photo];//这句代码执行时,它所在队列中没有其他代码块与之同时执行。因此受到保护,不会有人在读
dispatch_async(dispatch_get_main_queue(), ^{
//通知主线程任务写操作完成
});
});
}
}

- (NSArray *)photos {
__block NSArray*array;
//为保证返回有结果,此处使用同步
dispatch_sync(queue, ^{
array =[NSArray arrayWithArray:_photoArray];
});
return array;
}</span>


7、dispatch_apply( )
按照函数指定的次数将指定代码块添加到指定的队列中,并等待全部任务结束;
dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
第一个参数为指定次数
*/
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"done");

//运行结果
4
2
3
0
1
done


8、 dispatch_once( )
用于保证应用程序中只执行一次指定的代码块,以单例为例说明;

<span style="font-size:18px;">static PersonData *instance = nil;
- (instancetype)sharePersonData {

if (self) {
staticdispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[PersonData alloc] init];
//dosomething
});
}
return self;
}</span>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: