文章

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。通过该函数,即使在多行程环境下,可以保证百分百安全。

本文由作者按照 CC BY 4.0 进行授权