GCD读书笔记
定义
Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想要执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并执行任务。由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样就比以前的线程更有效率。
串行和并行决定执行顺序,串行是按顺序执行,并行反之。同步和异步决定开不开线程,同步不开,并且一定在主线程执行,异步不一定开。
API
1. 创建队列
创建串行队列:
1
dispatch_queue_t mySerialQueue = dispatch_queue_create("com.example.GCD.mySerialQueue", NULL);
创建并行队列:
1
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.example.GCD.myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
-
第一个参数是队列名,推荐使用应用程序ID这种逆序全程域名(FQDN fully qualified domain name)。
-
第二个参数为dispatch_queue_attr_t类型,如果需要生成串行队列,则填入NULL(或者DISPATCH_QUEUE_SERIAL),如果需要生成并行队列,则填入DISPATCH_QUEUE_CONCURRENT。
在《Objective-C高级编程》一书中,提到
遗憾的是尽管有ARC这一通过编译器自动管理内存的优秀技术,但生成的Dispatch Queue必须由程序员负责释放。这是因为Dispatch Queue并没有像Block那样具有作为Objective-C对象来处理的技术。
事实上,现在在代码中添加
1
dispatch_release(mySerialQueue);
会报‘release’ is unavailable: not available in automatic reference counting mode。也就是说,当前的ARC已经把Dispatch Queue当成对象来处理了。
为了避免数据竞争,写操作应该尽量在串行队列 中,而读操作则可以在并行队列中。
2. 获取队列
获取主队列:
1
dispatch_queue_t mainQueue = dispatch_get_main_queue();
获取全局队列:
1
2
3
4
5
6
7
/*
#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
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3. 变更优先级
使用dispatch_queue_create生成的队列,默认优先级与DISPATCH_QUEUE_PRIORITY_DEFAULT的全局队列一样。而要变更队列的优先级,要找到与之对应优先级的全局队列(如果不是全局队列,则会作为执行阶层,加入到目标队列中),并使用dispatch_set_target_queue变更。
1
dispatch_set_target_queue(mySerialQueue, globalQueue);
- 第一个参数为需要修改优先级的队列,不可以填入全局队列或者主队列
- 第二个为目标队列
将Dispatch Queue指定为dispatch_set_target_queue 函数的参数,不仅可以变更Dispatch Queue的执行优先级,还可以作成Dispatch Queue的执行阶层。如果在多个Serial Dispatch Queue中用dispatch_set_target_queue函数指定目标为Serial Dispatch Queue,那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理。
事实上,多个并行队列和串行队列追加到一个串行队列上,都会在目标Serial Dispatch Queue上只能同时执行一个处理。
1
2
3
4
5
6
7
8
9
dispatch_queue_t mySerialQueue1 = ldispatch_queue_create("com.example.GCD.mySerialQueue1", NULL);
dispatch_queue_t mySerialQueue2 = dispatch_queue_create("com.example.GCD.mySerialQueue2", DISPATCH_QUEUE_CONCURRENT);
//假如没有追加到mySerialQueue,blk0和blk1的执行顺序是不确定的。
dispatch_set_target_queue(mySerialQueue1, mySerialQueue);
dispatch_set_target_queue(mySerialQueue2, mySerialQueue);
dispatch_async(mySerialQueue1, blk0);
dispatch_async(mySerialQueue2, blk1);
4. dispatch_after
1
2
3
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//TODO
});
因为Main Dispatch Queue在主线程Runloop中执行,所以在比如每隔1/60秒执行的Runloop中,Block最快在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理追加或主线程的处理本身有延迟时,这个时间会更长。
5. Dispatch Group
想要在多个Dispatch Queue结束时,添加结束处理,可以使用Dispatch Group。
1
2
3
4
5
6
7
8
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_async(global, blk0);
dispatch_async(global, blk1);
dispatch_async(global, blk2);
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"END");
});
NSLog(@”END”); 一定会在blk0,blk1,blk2执行完后再执行。
6. dispatch_barrier_async
dispatch_barrier_async函数,会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中。然后在由dispatch_barrier_async追加的处理执行完毕后,Concurrent Dispatch Queue才恢复为一般的动作,追加到该Concurrent Dispatch Queue的处理又开始并行执行。
1
2
3
4
5
dispatch_async(myConcurrentQueue, blk0);
dispatch_async(myConcurrentQueue, blk1);
dispatch_barrier_async(myConcurrentQueue, blk0);
dispatch_async(myConcurrentQueue, blk2);
//开始可能会先打印0或1,然后dispatch_barrier_async让blk0先处理,最后再处理blk2
注意,经过测试,栅栏函数要添加到dispatch_queue_create创建的队列中,不然与dispatch_async无异。
7. dispatch_sync
dispatch_sync将指定的block同步地追加到Dispatch Queue中,在追加的block结束之前,dispatch_sync函数会一直等待。
有两种情况容易产生死锁:
1
2
3
4
5
//该代码在主线程中执行指定的Block,并等待其执行结束。而其实在主线程正在执行这些源代码,
//所以无法执行追加到主线程的Block,造成了死锁。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello");
});
1
2
3
4
5
dispatch_async(mySerialQueue, ^{
dispatch_sync(mySerialQueue, ^{
NSLog(@"hello");
});
});
8. dispatch_apply
dispatch_apply可以按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部执行结束。
1
2
3
dispatch_apply(9, global, ^(size_t index) {
NSLog(@"%zu", index);
});
由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。
9. dispatch_supend / dispatch_resume
dispatch_supend挂起指定的队列,dispatch_resume恢复指定的队列。
10. dispatch_semaphore_t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//参数value必须大于等于0
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *arr = [[NSMutableArray alloc] init];
for (int i = 0; i < 100; i++) {
dispatch_async(global, ^{
//此方法会使信号量value-1,当value=0时,调用此方法会阻塞线程
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[arr addObject:@(i)];
NSLog(@"%ld", arr.count);
//此方法会发送一个信号量使value+1
dispatch_semaphore_signal(semaphore);
});
}
11. dispatch_once
dispatch_once函数式保证在应用程序中只执行一次指定处理的API。通过该函数,即使在多行程环境下,可以保证百分百安全。