GCD是iOS中多线程的一种实现方法,使用起来比较方便,但是有些要点还是需要注意。
(一)、队列有三种:主队列-主线程的队列,串行执行;全局队列-global队列,并行执行;自己创建的队列-串行或并行队列,由标志位决定。 但是注意使用dispatch_sync全部是串行执行。
(二)、GCD中经常使用到的是dispatch_async和dispatch_sync,二者多线程的实现还是有一定区别的。
1. dispatch_async 调用global的队列,那么有几个dispatch_async 就会创建几个子线程并行执行。
2. dispatch_async 调用自己创建的队列(串行队列),则dispatch_async 调用该队列时,只创建一个新的线程,在线程内部串行执行。
3. dispatch_sync 调用任何队列,都是在调用的线程中串行执行,并不创建新的子线程。
也就是说同步函数dispatch_sync不具备创建线程的能力,异步函数dispatch_async根据不同的使用创建不同的线程,也有可能不开线程,比如使用主队列时。
常见的应用模式是:
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行耗时的异步操作…
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执⾏UI刷新操作
});
或者
[self performSelectorOnMainThread:@selector(test:) withObject:obj waitUntilDone:NO];
});
使用 dispatch_sync需注意死锁的情况,若dispatch_sync在同步同一个队列,则将会发生死锁,程序无法继续执行 。
NSLog(@”主线程–%@”,[NSThread currentThread]); dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@”First Log”); //永远无法执行
NSLog(@”子线程–%@”,[NSThread currentThread]);
});
NSLog(@”Second Log”); //永远无法执行
(三)、GCD中还有dispatch_after函数,实际使用中是否会开启子线程会根据选择的队列决定。多用于主线程
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
延时在主线程中执行的代码
NSLog(@”延时线程–%@”,[NSThread currentThread]);
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0,0), ^{
延时在子线程中执行的代码
NSLog(@”延时线程–%@”,[NSThread currentThread]);
});
需要注意的是,dispatch_after一旦返回就无法再取消了。另外需要明确的是dispatch_after并不是在制定时间后立即执行,而只是在指定时间追加处理到指定的队列中。 一般不精确的延时可以使用。
(四)、使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
整个程序运行过程中,只会执行一次。一般用于实现单例,但需要注意的是只是让类实例访问时线程安全,类本身的操作是否线程安全需要自己控制
(五)、创建队列组可以先并行执行一些操纵,最后等所有并行执行之后,同步处理最后等结果。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, globalQueue, ^{
子线程操作1。。。
});
dispatch_group_async(group, globalQueue, ^{
子线程操作2。。。
});
或者
dispatch_group_enter(group);
需要并行的操作
dispatch_group_leave(group);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_group_notify(group,mainQueue, ^{
回到主线程处理操作1和2的结果
});
(六)、GCD 通过用 dispatch_barrier_async 创建一个读写锁,解决读写的问题。由于在主队列和自定义串行队列中不起作用,而全局队列又可能会无意间影响其他操作,所以一般用于自定义的并发序列,
@interface PhotoManager ()
@property (nonatomic,strong,readonly) NSMutableArray *photosArray;
@property (nonatomic, strong) dispatch_queue_t concurrentPhotoQueue; ///< Add this
@end
写操作
- (void)addPhoto:(Photo *)photo
{
if (photo) { //写操作必须串行
dispatch_barrier_async(self.concurrentPhotoQueue, ^{
[_photosArray addObject:photo];
dispatch_async(dispatch_get_main_queue(), ^{
[self postContentAddedNotification];
});
});
}
}
读操作
- (NSArray *)photos
{
__block NSArray *array;
dispatch_sync(self.concurrentPhotoQueue, ^{ // 读操作其实可以并行也可以串行
array = [NSArray arrayWithArray:_photosArray];
});
return array;
}
实例化队列属性
+ (instancetype)sharedManager
{
static PhotoManager *sharedPhotoManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedPhotoManager = [[PhotoManager alloc] init];
sharedPhotoManager->_photosArray = [NSMutableArray array];
sharedPhotoManager->_concurrentPhotoQueue = dispatch_queue_create(“com.selander.GooglyPuff.photoQueue”,
DISPATCH_QUEUE_CONCURRENT);
});
return sharedPhotoManager;
}
关于dispatch_barrier_async,这里想多说一下,dispatch_barrier_async会保证之前队列中的并行操作全部执行完后,在这执行一个串行的操作,之后再进行其他加入队列中的并行操作。
例如:
dispatch_queue_t queue = dispatch_queue_create(“test”, DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@”test 1”);
});
dispatch_async(queue, ^{
NSLog(@”test 2”);
});
dispatch_barrier_async(queue, ^{
NSLog(@”test 3”);
});
NSLog(@”test 0”);
dispatch_async(queue, ^{
NSLog(@”test 4”);
});
dispatch_async(queue, ^{
NSLog(@”test 5”);
});
对于上述代码,test 1和test 2并行,同时也与test 0并行,test 3是在test1和test2之后执行,但是test 4和test 5 一定是在test3之后执行。
关于开辟线程的情况,这里由于有先后顺序可能会复用线程。
test 1和test 2分别使用线程2和3, 由于test 3一定是在test 2执行之后执行,所以test 3可能仍然使用2或者3(谁最后结束用谁),而test 4和test 5一定会在test 3之后执行,所以他们其中一个可能会复用test 3的线程(即线程2或者3),而另外一个就开辟一个新的线程。
复用的线程做法很优雅,减少了开销,同时对开发者又是透明的。
(六)、GCD的其它函数
dispatch_apply 按照指定的次数将block追加到指定的队列中,并等待全部处理结束才返回。
dispatch_suspend 挂起指定的队列
dispatch_resume 恢复指定的队列
dispatch semaphore 更细粒度的控制
dispatch I/O和dispatch Data可实现将大文件分成若干小的文件并行执行。