GCD深入理解
2015-09-04 14:33
281 查看
https://github.com/nixzhu/dev-blog/blob/master/2014-05-14-grand-central-dispatch-in-depth-part-2.md读书笔记。
1、串行队列:串行队列中的任务一次执行一个,每个任务只在前一个任务完成时才开始。而且你不知道一个block结束和下一个block开始之间的时间长度,这些任务的执行时机受到
GCD 的控制;唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。如下图所示:
2、并发队列:在并发队列中的任务能得到的保证只是它们会按照被添加的顺序开始执行,任务可能以任意顺序完成,你不知道何时开始运行下一个任务,或者任意时刻有多少Block在运行,这完全取决于GCD。
3、队列类型:主队列(main queue)、全局调度队列(global dispatch queue)、自定义队列
主队列:串行队列,唯一可用于更新UI的线程,用于发生消息给UIView或者发生通知的;
全局调度队列:并发队列,优先级包括background、low、default、high;
自定义队列:可以创建串行队列或者并发队列。
4、GCD 的“艺术”归结为选择合适的队列来调度函数以提交你的工作。
5、用dispatch_async处理后台任务:通常在viewDidLoad函数中容易加入太多杂乱的工作(too much clutter),这会引起试图控制器出现前更长的等待,最好是卸下一些工作放到后台。dispatch_async添加一个block到队列就立即返回,任务会在之后由 GCD 决定执行。当你需要在后台执行一个基于网络或 CPU 紧张的任务时就使用 dispatch_async ,这样就不会阻塞当前线程。
6、使用 dispatch_after 延后工作:dispatch_after 工作起来就像一个延迟版的 dispatch_async 。你依然不能控制实际的执行时间,且一旦 dispatch_after 返回也就不能再取消它。用法如下:
double delayInSeconds = 1.0;
// 延迟的时长
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// 这里做需要延迟的操作
});
注意:dispatch_after最好的选择是在主队列中。
7、dispatch_once让你的单例线程安全:
+ (instancetype)sharedManager
{
static YourClass *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[YourClass alloc] init];
// 其他操作
});
return sharedManager;
}
dispatch_once() 以线程安全的方式执行且仅执行其代码块一次。试图访问临界区(即传递给 dispatch_once 的代码)的不同的线程会在临界区已有一个线程的情况下被阻塞,直到临界区完成为止。dispatch_once是整个app生命周期内只执行一次,不是对象生命周期内只执行一次。
8、dispatch_barriers处理读者和写者问题:
众多的Foundation框架中的类都不是线程安全的,例如NSMutableArray、NSMutableDictionary等,以下是非线程安全的框架类:
以下考虑NSMutableArray,多个线程同时读取(多个读者)NSMutableArray的一个实例是不会产生问题的,但是一个线程正在读取时另外一个线程修改数组(读者写者同时)就是不安全的。
GCD 通过用 dispatch_barriers 创建一个读者写者锁,提供了一个优雅的解决方案。Dispatch_barriers 是一组函数,在并发队列上工作时扮演一个串行式的瓶颈。使用 GCD 的障碍(barrier)API 确保提交的 Block 在那个特定时间上是指定队列上唯一被执行的条目。这就意味着所有的先于调度障碍提交到队列的条目必能在这个 Block 执行前完成。当这个 Block 的时机到达,调度障碍执行这个 Block 并确保在那个时间里队列不会执行任何其它 Block 。一旦完成,队列就返回到它默认的实现状态。 GCD 提供了同步和异步两种障碍函数。
下图显示了障碍函数对多个异步队列的影响:
注意:dispatch_barriers通常都是用在自定义并发队列,创建一个自己的队列去处理障碍函数并分开读和写函数,且这个并发队列将允许多个读操作同时进行。
@property (nonatomic, strong) dispatch_queue_t concurrentQueue; //
声明一个自定义队列
self.concurrentQueue = dispatch_queue_create(“队列的标识”,DISPATCH_QUEUE_CONCURRENT);
// 初始化自定义队列
// 写者
- (void)addPhoto:(Photo *)photo
{
if (photo) { // 1
dispatch_barrier_async(self.concurrentQueue, ^{ // 2
[_photosArray addObject:photo]; // 3.这个block不会同时和其他block一起在concurretnQueue队列中一起执行
dispatch_async(dispatch_get_main_queue(), ^{ // 4
[self postContentAddedNotification];
});
});
}
}
// 读者
- (NSArray *)photos
{
__block NSArray *array; // 1
dispatch_sync(self.concurrentQueue, ^{ // 2
array = [NSArray arrayWithArray:_photosArray]; // 3
});
return array;
}
9、调度组dispatch_groups:
Dispatch Group 会在整个组的任务都完成时通知你。这些任务可以是同步的,也可以是异步的,即便在不同的队列也行。而且在整个组的任务都完成时,Dispatch Group 可以用同步的或者异步的方式通知你。因为要监控的任务在不同队列,那就用一个 dispatch_group_t 的实例来记下这些不同的任务。当组中所有的事件都完成时,GCD 的 API 提供了两种通知方式。第一种是 dispatch_group_wait ,它会阻塞当前线程,直到组里面所有的任务都完成或者等到某个超时发生。第二种不会阻塞进程,下面只考虑第二种方式。
// 函数实现从网络下载三张图片的操作,当三张图片都下载完成后弹出完成的对话框
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
// 1
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup); // dispatch_group_enter 手动通知 Dispatch Group 任务已经开始。你必须保证 dispatch_group_enter 和 dispatch_group_leave 成对出现,否则你可能会遇到诡异的崩溃问题
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 3
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // dispatch_group_notify 以异步的方式工作。当 Dispatch Group 中没有任何任务时,它就会执行其代码,那么
completionBlock 便会运行。你还指定了运行 completionBlock 的队列,此处,主队列就是你所需要的。
if (completionBlock) {
completionBlock(error);
}
});
}
10、之后该往哪儿去:
关于如何优化你的应用,参看 如何使用
Instruments 教程
同样请看看 如何使用 NSOperations 和 NSOperationQueues 吧,它们是建立在
GCD 之上的并发技术。
1、串行队列:串行队列中的任务一次执行一个,每个任务只在前一个任务完成时才开始。而且你不知道一个block结束和下一个block开始之间的时间长度,这些任务的执行时机受到
GCD 的控制;唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。如下图所示:
2、并发队列:在并发队列中的任务能得到的保证只是它们会按照被添加的顺序开始执行,任务可能以任意顺序完成,你不知道何时开始运行下一个任务,或者任意时刻有多少Block在运行,这完全取决于GCD。
3、队列类型:主队列(main queue)、全局调度队列(global dispatch queue)、自定义队列
主队列:串行队列,唯一可用于更新UI的线程,用于发生消息给UIView或者发生通知的;
全局调度队列:并发队列,优先级包括background、low、default、high;
自定义队列:可以创建串行队列或者并发队列。
4、GCD 的“艺术”归结为选择合适的队列来调度函数以提交你的工作。
5、用dispatch_async处理后台任务:通常在viewDidLoad函数中容易加入太多杂乱的工作(too much clutter),这会引起试图控制器出现前更长的等待,最好是卸下一些工作放到后台。dispatch_async添加一个block到队列就立即返回,任务会在之后由 GCD 决定执行。当你需要在后台执行一个基于网络或 CPU 紧张的任务时就使用 dispatch_async ,这样就不会阻塞当前线程。
6、使用 dispatch_after 延后工作:dispatch_after 工作起来就像一个延迟版的 dispatch_async 。你依然不能控制实际的执行时间,且一旦 dispatch_after 返回也就不能再取消它。用法如下:
double delayInSeconds = 1.0;
// 延迟的时长
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// 这里做需要延迟的操作
});
注意:dispatch_after最好的选择是在主队列中。
7、dispatch_once让你的单例线程安全:
+ (instancetype)sharedManager
{
static YourClass *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[YourClass alloc] init];
// 其他操作
});
return sharedManager;
}
dispatch_once() 以线程安全的方式执行且仅执行其代码块一次。试图访问临界区(即传递给 dispatch_once 的代码)的不同的线程会在临界区已有一个线程的情况下被阻塞,直到临界区完成为止。dispatch_once是整个app生命周期内只执行一次,不是对象生命周期内只执行一次。
8、dispatch_barriers处理读者和写者问题:
众多的Foundation框架中的类都不是线程安全的,例如NSMutableArray、NSMutableDictionary等,以下是非线程安全的框架类:
以下考虑NSMutableArray,多个线程同时读取(多个读者)NSMutableArray的一个实例是不会产生问题的,但是一个线程正在读取时另外一个线程修改数组(读者写者同时)就是不安全的。
GCD 通过用 dispatch_barriers 创建一个读者写者锁,提供了一个优雅的解决方案。Dispatch_barriers 是一组函数,在并发队列上工作时扮演一个串行式的瓶颈。使用 GCD 的障碍(barrier)API 确保提交的 Block 在那个特定时间上是指定队列上唯一被执行的条目。这就意味着所有的先于调度障碍提交到队列的条目必能在这个 Block 执行前完成。当这个 Block 的时机到达,调度障碍执行这个 Block 并确保在那个时间里队列不会执行任何其它 Block 。一旦完成,队列就返回到它默认的实现状态。 GCD 提供了同步和异步两种障碍函数。
下图显示了障碍函数对多个异步队列的影响:
注意:dispatch_barriers通常都是用在自定义并发队列,创建一个自己的队列去处理障碍函数并分开读和写函数,且这个并发队列将允许多个读操作同时进行。
@property (nonatomic, strong) dispatch_queue_t concurrentQueue; //
声明一个自定义队列
self.concurrentQueue = dispatch_queue_create(“队列的标识”,DISPATCH_QUEUE_CONCURRENT);
// 初始化自定义队列
// 写者
- (void)addPhoto:(Photo *)photo
{
if (photo) { // 1
dispatch_barrier_async(self.concurrentQueue, ^{ // 2
[_photosArray addObject:photo]; // 3.这个block不会同时和其他block一起在concurretnQueue队列中一起执行
dispatch_async(dispatch_get_main_queue(), ^{ // 4
[self postContentAddedNotification];
});
});
}
}
// 读者
- (NSArray *)photos
{
__block NSArray *array; // 1
dispatch_sync(self.concurrentQueue, ^{ // 2
array = [NSArray arrayWithArray:_photosArray]; // 3
});
return array;
}
9、调度组dispatch_groups:
Dispatch Group 会在整个组的任务都完成时通知你。这些任务可以是同步的,也可以是异步的,即便在不同的队列也行。而且在整个组的任务都完成时,Dispatch Group 可以用同步的或者异步的方式通知你。因为要监控的任务在不同队列,那就用一个 dispatch_group_t 的实例来记下这些不同的任务。当组中所有的事件都完成时,GCD 的 API 提供了两种通知方式。第一种是 dispatch_group_wait ,它会阻塞当前线程,直到组里面所有的任务都完成或者等到某个超时发生。第二种不会阻塞进程,下面只考虑第二种方式。
// 函数实现从网络下载三张图片的操作,当三张图片都下载完成后弹出完成的对话框
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
// 1
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup); // dispatch_group_enter 手动通知 Dispatch Group 任务已经开始。你必须保证 dispatch_group_enter 和 dispatch_group_leave 成对出现,否则你可能会遇到诡异的崩溃问题
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 3
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // dispatch_group_notify 以异步的方式工作。当 Dispatch Group 中没有任何任务时,它就会执行其代码,那么
completionBlock 便会运行。你还指定了运行 completionBlock 的队列,此处,主队列就是你所需要的。
if (completionBlock) {
completionBlock(error);
}
});
}
10、之后该往哪儿去:
关于如何优化你的应用,参看 如何使用
Instruments 教程
同样请看看 如何使用 NSOperations 和 NSOperationQueues 吧,它们是建立在
GCD 之上的并发技术。
相关文章推荐
- B树、B-树、B+树、B*树都是什么
- 【Windows编程】系列第十一篇:多文档界面框架
- Android官方文档系列(翻译)
- ac自动机模板-kuangbin
- UVA10557SPFA或者bfs+dfs
- shader中的属性
- 小度wifi在window server2008R2系统下创建不了
- mysqladmin创建MySQL数据库
- leetcode H-Index
- $GLOBALS['HTTP_RAW_POST_DATA'] 和$_POST的区别
- 【Windows编程】系列第十一篇:多文档界面框架
- 让UIScrollView只有一边有弹簧效果
- Codeforces Round #304 (Div. 2)
- Rhythmbox中文乱码问题的解决
- Objective-C Programming (2nd Edition)
- 华为机试测试-dna-字符串
- 黑马程序员 JAVA基础学习日记五——封装 继承 多态
- addevent()实现跨浏览器绑定事件
- Android实例-解决虚拟键盘遮挡问题(XE8+小米2)
- JAVA多线程面试