GCD术语
- 串行和并行
- 同步和异步
- 关键段
- 竞争条件
- 死锁
- 线程安全
- 线程上下文切换
平行与并发
- 平行:多个线程可以同时执行
- 并发:如果是多核的则多个线程可以同时执行,但是如果是单核的,就会涉及到线程上下文切换,足够快的切换,可以造成一种假象就是在同时执行。
分发队列
GCD提供了分发队列去处理代码块,这些任务会以先进先出的方式执行。所有的分发队列自己是线程安全的,你可以同时从不同的线程去获取他们。下面会介绍两种GCD提供的特殊队列:
线性队列
在线性队列中的任务一次只执行一个,每个任务只在上个任务执行完成后开始执行,你不能确保每个代码块执行的时间,但是这些任务会按照添加到队列中的顺序进行执行。在一个线性队列的两个任务不可能同时执行。
并发队列
并发队列中的任务会以添加到队列中的顺序进行执行,但是你不能确保他们以何种顺序结束。所以你无法确保同一时间有多少个任务正在执行。
队列类型
首先,系统提供了一个叫做“main queue”的特殊线性队列,只可以在这个线程中更新你的UI,这个队列用来向UIViews发送消息或者发布通知。系统也提供了几个并发队列-“Global Dispatch Queues”,它们是四种拥有不同优先级的全局队列,优先级为“background, low, default, high”。应该意识到Appple's API也在使用这些队列,所以你添加到这些队列的任何任务不是这些队列中的唯一任务。 最后,你可以创建你自己的线性或者并发队列。这意味着你至少有五种队列:主队列,四种全局分发队列,加之你可以自定义的队列。
- 主队列:dispatch_get_main_queue()
- 全局队列: dispatch_global_queue(优先级选项)
优先级:- DISPATCH_QUEUE_PRIORITY_HIGH
- DISPATCH_QUEUE_PRIORITY_LOW
- DISPATCH_QUEUE_PRIORITY_BACKGROUND
- DISPATCH_QUEUE_PRIORITY_DEFAULT
diapatch_async
dispatch_async在一个队列中增加一个block并且立即返回。在block中的task将在之后特定的时间被执行。在执行基于网络的或者cpu密集型任务的时候应该使用dispatch_async在后台执行。
不同队列类型的使用指南
- 自定义的线性队列:当你想线性执行后台任务,并且跟踪此任务的的时候。-dispatch_sync
- 主队列:更新UI
- 并发队列:在后台执行非UI工作。
使用dispatch_after
延迟工作
在延迟的一段时间后再执行。
double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_MSEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (!count) {
[self.navigationItem setPrompt:@"Add photos with faces to Googlyiy them!"];
} else {
[self.navigationItem setPrompt:nil];
}
});
什么时候适合用dispatch_after:
- Main Queue
在使用单例模式时要注意线程安全
单例经常在同一时间被多个控制器使用。
为保证单例初始化代码一次只执行一个,可以使用dispatch_once
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPhotoManager = [[PhotoManager alloc] init];
sharedPhotoManager->_photosArray = [NSMutableArray array];
});
return sharedPhotoManager;
}
dispatch_once()以线程安全的方式一次只执行一个block。
注:以上的代码只是保证以线程安全的方式获取共享实例,没有使类线程安全。
读者写者问题
许多可变的对象,如NSMutableArray,不能保证在一个线程读的时候,其他线程不能写。
GCD提供了一种优雅的解决方式:读写锁 dispatch barriers
读者:
- (void)addPhoto:(Photo *)photo
{
if (photo) { // 1
dispatch_barrier_async(self.concurrentPhotoQueue, ^{ // 2
[_photosArray addObject:photo]; // 3
dispatch_async(dispatch_get_main_queue(), ^{ // 4
[self postContentAddedNotification];
});
});
}
}
什么时候适合用dispatch barriers
- 自定义的同步队列 custom concurrent queue
写者:为确保读者的线程安全,写者要和读者在同一个分发队列中,使用dispatch_sync
去等待读完成。
什么时候适合用dispatch_sync
- 自定义的同步队列 custom concurrent queue
eg:
- (NSArray *)photos
{
__block NSArray *array; // 1
dispatch_sync(self.concurrentPhotoQueue, ^{ // 2
array = [NSArray arrayWithArray:_photosArray]; // 3
});
return array;
}
Dispatch Groups
Dispatch groups在一组task全部完成之后通知你。这些任务可以是同步或者异步的甚至在不同队列中。
在组里的所有事件完成的时候,GCD API提供了两种两种方式进行通知。
-
dispatch_group_wait 这个函数会阻塞你当前的线程直到在组里的所有任务都完成之后。
相关方法:dispatch_group_t downloadGroup = dispatch_group_create(); 创建一个group
dispatch_group_enter(downloadGroup); 进入
dispatch_group_leave(downloadGroup); 离开
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); 等待
dispatch_group_notify 这个函数不会阻塞你当前的线程,在组里的所有任务完成之后,它会异步的通知你。
Dispatch_apply
dispatch_apply扮演了for循环的角色,它会同时执行循环中的各个条件。
什么时候适合用dispatch_apply
- 自定义的线性队列, 同步队列
eg:
dispatch_apply(3, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t 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);
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup);
}];
[[PhotoManager sharedManager] addPhoto:photo];
});