GCD(Grand Central Dispatch)是iOS多任务的核心,它可以让程序员不用直接参与到线程的创建和维护中,而让操作系统来直接处理线程的创建和管理,让多任务的开发更加简单。
操作和队列
在GCD中,操作就是一个代码块(block),而队列(dispatch_queue)就是存放这些操作的地方,要注意,队列并不是线程,队列是组织操作的,调用dispatch_async并不会让操作开始执行,而只是把操作添加到队列的末尾,当操作到达队列头时,操作才会开始执行。添加到队列中的操作不能取消。
创建队列
可以使用dispatch_get_main_queue()得到主队列,主队列中的操作是一直在主线程中执行的,不管是同步执行还是异步执行;使用dispatch_queue_create(constchar *label,dispatch_queue_attr_t attr)来创建队列,第一个参数是队列的标示符,第二个参数则指定队形是串行队列(DISPATCH_QUEUE_SERIAL)还是并发队列(DISPATCH_QUEUE_CONCURRENT),不管是串行队列还是并发队列,队列中的操作都采用FIFO的方式取出执行,不同的是并发队列可以将队列中的多个操作取出并放到不同的线程中执行。主队列也是一个串行队列,系统中存在有全局并发队列,使用dispatch_get_global_queue(long identifier, unsigned long flags)获取全局并发队列,使用第一个参数选择优先级:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
GCD会根据可用线程尽可能的从高优先级队列取出操作并执行,等高优先级队列空了以后,继续从默认优先级队列中取出操作并执行,以此类推。
执行操作
操作有两种执行方式:同步执行与异步执行,使用dispatch_async(dispatch_queue_t queue, dispatch_block_t block)(异步执行)不阻塞当前线程,并将block添加到queue中,而dispatch_sync(dispatch_queue_t queue, dispatch_block_t block)(同步执行)则会先阻塞当前的线程,再将block添加到queue中,等block执行完了,当前线程才能继续执行。尽可能的少使用同步执行,因为同步执行很容易会出现死锁的情况:
dispatch_sync(dispatch_get_main_queue(), ^{
//同步执行
NSLog(@"sync");
});
这段代码看似没有问题,但如果当前线程是主线程的话,则会先阻塞主线程,等待block执行完,而block却是在主线中执行的,所以block中的代码永远不会被执行,而且主线程也会被一直阻塞。
可以使用dispatch_suspend(dispatch_object_t object)来暂停队列,要注意,正在执行的操作(block)不会被暂停,只会暂停还未开始执行的操作;对应的,使用dispatch_resume(dispatch_object_t object)来恢复执行队列。多次暂停队列需要等量的恢复操作。
使用GCD代替同步锁
在开发中,如果有多个线程执行同一代码,可能会出现一些问题,而解决方案就是给代码添加同步锁。在GCD出现以前,可以使用@synchronized(object)给object创建一个锁,这种方法很容易使用,但是滥用会造成代码效率低下。而使用GCD可以以更简单、更高效的方式给代码加锁。比如在setter和getter中:
- (NSString *)aString
{
__block NSString* str;
dispatch_sync(_myQueue, ^{
str = _aString;
});
return str;
}
- (void)setAString:(NSString *)aString
{
dispatch_barrier_async(_myQueue, ^{
_aString = aString;
});
}
其中_myQueue是使用dispatch_queue_create创建的并发队列,在获取方法里将操作放入_myQueue里使用同步执行,可以保证获取方法在操作执行完成后才能再次被使用;而dispatch_barrier_async可以将_myQueue阻塞,并在操作执行完成后再取消阻塞,这样可以保证在执行设置方法时不能执行获取方法。
队列组
队列组(dispatch_group_t)可以将任务进行分组并执行,当这组任务都执行完后,调用者可以收到通知。使用dispatch_group_create()创建队列组,使用
dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
将异步执行block添加到queue中执行,并表示block为group组中的任务;使用
dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
接收执行完成的通知(不会阻塞当前线程)。使用
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
等待group中的任务执行完(阻塞当前线程),并继续阻塞当前线程timeout的时间。
例如:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue1 = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"开始");
dispatch_group_async(group, queue1, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"操作1-%d", i);
}
});
dispatch_group_async(group, queue2, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"操作2-%d", i);
}
});
dispatch_group_async(group, queue3, ^{
for (int i = 0; i < 3; i++) {
NSLog(@"操作3-%d", i);
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"更新UI");
});
先创建了一个dispatch_group_t和三个并发队列,再将三个并行任务加入组并执行,执行完后通知主队列更新UI。打印结果:
2015-08-12 14:28:08.275 GCDTest[2248:88609] 开始
2015-08-12 14:28:08.276 GCDTest[2248:88635] 操作1-0
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作1-1
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作1-2
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作2-0
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作2-1
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作2-2
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作3-0
2015-08-12 14:28:08.277 GCDTest[2248:88635] 操作3-1
2015-08-12 14:28:08.278 GCDTest[2248:88635] 操作3-2
2015-08-12 14:28:08.286 GCDTest[2248:88609] 更新UI
使用GCD实现单例模式
在GCD出现以前,实现单例模式需要使用@synchronized来保证线程安全,这样做除了应用开销外还存在一些争议。而使用GCD可以更简单的实现单例模式,解决方法就是dispatch_once。具体用法就是:
static GCDTest *test;
+(id)sharedInstance
{
static dispatch_once_t once;
dispatch_once(&once, ^{
test = [[GCDTest alloc] init];
});
return test;
}
因为每次调用时都需要使用相同的dispatch_once_t,所以需要声明为static。
使用dispatch_once可以保证线程安全,并且简化代码。使用dispatch_once也更高效,它没有使用重量级的同步机制,而是采用“原子访问”(atomic access)来查询dispatch_once_t来判断其所对应的代码是否已执行过。