GCD 中那些你可能不熟悉的知识

dispatch_get_global_queue 的参数的含义

dispatch_get_global_queue 用来从 GCD 的全局队列池中获取一个全局的队列。

全局队列都是并发队列。

第一个参数

第一个参数在 iOS 7 及更低版本上表示优先级,有四种取值,定义如下:

1
2
3
4
#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

在 iOS 8 及更高版本上表示服务质量(QoS),有六种取值,定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__QOS_ENUM(qos_class, unsigned int,
QOS_CLASS_USER_INTERACTIVE
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x21,
QOS_CLASS_USER_INITIATED
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x19,
QOS_CLASS_DEFAULT
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x15,
QOS_CLASS_UTILITY
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x11,
QOS_CLASS_BACKGROUND
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x09,
QOS_CLASS_UNSPECIFIED
__QOS_CLASS_AVAILABLE(macos(10.10), ios(8.0)) = 0x00,
);

DISPATCH_QUEUE_PRIORITY_HIGH 和 QOS_CLASS_USER_INTERACTIVE 都是最高的优先级。

DISPATCH_QUEUE_PRIORITY_BACKGROUND 和 QOS_CLASS_BACKGROUND 的优先级非常低,一般不用,因为使用此优先级的任务很难被调度到。

如果需要兼容 iOS 7 和 iOS 8,可以往第一个参数中传入 0,在 iOS 7 中 0 是 DISPATCH_QUEUE_PRIORITY_DEFAULT 的取值,在 iOS 8 中,0 是 QOS_CLASS_UNSPECIFIED 的取值,所以都不会有问题。

第二个参数

第二个参数是苹果设计用来给未来做扩展用的,目前没有任何作用,开发中都是传入 0 即可。

dispatch_queue_create 的参数的含义

dispatch_queue_create 用来自己创建一个串行队列或并发队列。

第一个参数

第一个参数是队列名称,队列名称可以用来在 debug 或性能调优时在调用栈中显示队列名称,或者在 crash 日志中显示当前队列名,来帮助定位问题。通过查看系统创建的队列的命名方式,会发现苹果一般是用反域名风格来命名队列的。

第二个参数

第二个参数应该不陌生,就是传入 DISPATCH_QUEUE_SERIAL 来创建串行队列,传入 DISPATCH_QUEUE_CONCURRENT 来创建并发队列,也可以传入 NULL 这时也代表创建串行队列。

在队列中储存上下文信息

可以通过 dispatch_set_context 和 dispatch_get_context 来保存和获取自定义的上下文信息。dispatch_set_context 的参数是 void * 类型,可以传入任何 Objective-C 对象或者其它 C 指针类型等。

给队列设置 Finalizer Function 来清除上下文信息

队列的 Finalizer Function 会在队列销毁前调用,用 dispatch_set_finalizer_f 函数来给队列设置 Finalizer Function。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void myFinalizerFunction(void *context) {
MyDataContext *dataContext = (__bridge MyDataContext *)context;

// 清除上下文中的数据
[dataContext clean];
}

dispatch_queue_t createMyQueue() {
MyDataContext *context = [[MyDataContext alloc] init];

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.TaskQueue", NULL);
dispatch_set_context(serialQueue, (__bridge void *)(context));

// 设置 Finalizer Function
dispatch_set_finalizer_f(serialQueue, &myFinalizerFunction);

return serialQueue;
}

dispatch_async 和 dispatch_sync 的区别

函数 区别
dispatch_async 1. 会从线程池拿新线程并在新线程上执行任务。
2. 函数本身会立刻返回,继续执行后续代码。
dispatch_sync 1. 不会拿新线程,在当前线程上执行任务。
2. 函数本身会阻塞其它代码在当前线程上的执行,等到 Block 执行完成后,函数才会返回,继续执行后续代码。

死锁

对于串行队列,dispatch_sync 的调用方所在队列如果和 dispatch_sync 的第一个参数是同一个队列,就会造成死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)deadlock {
dispatch_queue_t queue = dispatch_queue_create("com.example.deadlock", DISPATCH_QUEUE_SERIAL);

dispatch_sync(queue, ^{
NSLog(@"这里 %@", [NSThread currentThread]);

dispatch_sync(queue, ^{
NSLog(@"这里2 %@", [NSThread currentThread]);
});

NSLog(@"come here");
});
}

上面代码会发生死锁,同样的,在主队列中 dispatch_sync 也会死锁:

1
2
3
4
5
6
7
- (void)deadlock {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"这里 1 %@", [NSThread currentThread]);
});

NSLog(@"这里 2 %@", [NSThread currentThread]);
}

dispatch_suspend 和 dispatch_resume

dispatch_suspend 挂起指定的队列,这时队列中的 task 会停止执行。

1
dispatch_suspend(queue);

dispatch_resume 恢复被挂起的指定队列,让队列中的任务继续执行。

1
dispatch_resume(queue);

这两个函数对应执行的或者正在执行的处理没有影响,只对追加到 Dispatch Queue 中但是尚未执行的处理有影响。

dispatch_set_target_queue

作用1:变更队列优先级

自己创建的队列可以指定优先级,但是全局队列的优先级总是默认的,可以通过 dispatch_set_target_queue 来改变全局队列的优先级。

1
2
3
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);

第一个参数:
指定要被变更优先级的队列

第二个参数:
指定一个队列,会把这个队列的优先级指定给第一个参数中的队列

作用2:让多个串行队列之间也能串行地执行任务

如果在多个 Serial Dispatch Queue 中用 dispatch_set_target_queue 函数指定目标为某一个 Serial Dispatch Queue,那么原先本应并行执行的多个 Serial Dispatch Queue,在目标 Serial Dispatch Queue 上只能同时执行一个处理。

在必须将不可并行执行的处理追加到多个 Serial Dispatch Queue 中时,如果使用 dispatch_set_target_queue 函数将目标指定为某一个 Serial Dispatch Queue,即可防止处理并行执行。

以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
dispatch_queue_t mySerialDispatchQueue1 = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue1", NULL);
dispatch_queue_t mySerialDispatchQueue2 = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue2", NULL);
dispatch_queue_t mySerialDispatchQueue3 = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue3", NULL);

dispatch_queue_t targetDispatchQueue = dispatch_queue_create("com.example.gcd.TargetDispatchQueue", NULL);

dispatch_set_target_queue(mySerialDispatchQueue1, targetDispatchQueue);
dispatch_set_target_queue(mySerialDispatchQueue2, targetDispatchQueue);
dispatch_set_target_queue(mySerialDispatchQueue3, targetDispatchQueue);

dispatch_async(mySerialDispatchQueue1, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"mySerialDispatchQueue1 %@ %@", @(i), [NSThread currentThread]);
}
});

dispatch_async(mySerialDispatchQueue2, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"mySerialDispatchQueue2 %@ %@", @(i), [NSThread currentThread]);
}
});

dispatch_async(mySerialDispatchQueue3, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"mySerialDispatchQueue3 %@ %@", @(i), [NSThread currentThread]);
}
});

的执行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2018-02-17 15:20:21.140237+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 0 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.140383+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 1 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.140470+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 2 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.140588+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 3 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.140672+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 4 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.140751+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 5 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.140834+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 6 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.140926+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 7 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.141006+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 8 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.141089+0800 GCDStudy[1509:141116] mySerialDispatchQueue1 9 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.141199+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 0 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.143468+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 1 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.143552+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 2 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.143645+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 3 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.143728+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 4 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.143805+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 5 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.143870+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 6 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.143940+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 7 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144002+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 8 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144067+0800 GCDStudy[1509:141116] mySerialDispatchQueue2 9 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144175+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 0 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144267+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 1 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144338+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 2 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144409+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 3 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144489+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 4 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144580+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 5 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144709+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 6 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144838+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 7 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.144971+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 8 <NSThread: 0x608000469840>{number = 3, name = (null)}
2018-02-17 15:20:21.145078+0800 GCDStudy[1509:141116] mySerialDispatchQueue3 9 <NSThread: 0x608000469840>{number = 3, name = (null)}

dispatch_apply

该函数按指定的次数将指定的 Block 追加到指定的 Dispatch Queue 中,并等待全部处理执行结束

例如下面代码:

1
2
3
4
5
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu %@", index, [NSThread currentThread]);
});
NSLog(@"done");

执行结果为:

1
2
3
4
5
6
7
8
9
10
11
2018-02-17 15:43:41.665070+0800 GCDStudy[1731:166365] 5 <NSThread: 0x60c000079c40>{number = 7, name = (null)}
2018-02-17 15:43:41.665065+0800 GCDStudy[1731:166331] 3 <NSThread: 0x604000075a40>{number = 1, name = main}
2018-02-17 15:43:41.665073+0800 GCDStudy[1731:166367] 1 <NSThread: 0x60000046d300>{number = 4, name = (null)}
2018-02-17 15:43:41.665073+0800 GCDStudy[1731:166402] 7 <NSThread: 0x60c0000799c0>{number = 9, name = (null)}
2018-02-17 15:43:41.665073+0800 GCDStudy[1731:166366] 4 <NSThread: 0x60000046d680>{number = 6, name = (null)}
2018-02-17 15:43:41.665079+0800 GCDStudy[1731:166379] 0 <NSThread: 0x60400007b0c0>{number = 3, name = (null)}
2018-02-17 15:43:41.665094+0800 GCDStudy[1731:166368] 2 <NSThread: 0x60400007b480>{number = 5, name = (null)}
2018-02-17 15:43:41.665095+0800 GCDStudy[1731:166401] 6 <NSThread: 0x604000079cc0>{number = 8, name = (null)}
2018-02-17 15:43:41.665218+0800 GCDStudy[1731:166331] 8 <NSThread: 0x604000075a40>{number = 1, name = main}
2018-02-17 15:43:41.665220+0800 GCDStudy[1731:166367] 9 <NSThread: 0x60000046d300>{number = 4, name = (null)}
2018-02-17 15:43:41.665643+0800 GCDStudy[1731:166331] done

每一次打印都是并发执行的,并且 done 一定会在最后打印。

实例:不需要关心顺序,遍历并处理 NSArray 的所有元素

1
2
3
4
5
6
NSArray *array = @[@1, @2, @3, @4, @5];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(array.count, queue, ^(size_t index) {
NSLog(@"%zu: %@", index, array[index]);
});

在不需要关心顺序的情况下,可以用 dispatch_apply 来遍历 NSArray,NSDictionary,NSSet 等,这种方式执行效率比直接循环遍历要高,但是可能更费电(因为要开辟和切换线程)。

由于 dispatch_apply 会阻塞当前线程,可以根据需要在 dispatch_async 中执行 dispatch_apply,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
NSArray *array = @[@1, @2, @3, @4, @5];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
dispatch_apply(array.count, queue, ^(size_t index) {
NSLog(@"%zu: %@", index, array[index]);
});

dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"done");
});
});

dispatch_apply 的实现方式

dispatch_apply 其内部是结合了 dispatch_sync 和 Dispatch Group 的调用来实现的。

Dispatch Semaphore

Dispatch Semaphore 是持有计数的信号量。

dispatch_semaphore_create 创建一个信号量,并通过参数指定信号量持有的计数。

dispatch_semaphore_signal 将信号量持有计数增加 1。

dispatch_semaphore_wait 函数会判断信号量持有计数的值,如果计数为 1 或大于 1,函数会直接返回。
如果计数为 0,函数会阻塞当前线程并一直处于等待状态不返回,直到信号量计数变为大于等于 1,dispatch_semaphore_wait 才会停止等待并返回。
dispatch_semaphore_wait 支持设置一个等待时间,如果到了这个时间,即使信号量计数不是大于等于 1,函数也会停止等待并返回。

实例1:让函数阻塞地执行异步任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)dispatchSemaphoreDemo {
dispatch_queue_t queue = dispatch_queue_create("com.example.GCD.dispatchSemaphore", NULL);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

dispatch_async(queue, ^{
NSLog(@"here 1");

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
dispatch_semaphore_signal(semaphore);
});
});

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

NSLog(@"here 2");
}

上面函数在执行过程是会先打印 here 1,然后阻塞住当前线程 1 秒钟,然后打印 here 2,然后函数返回,调用结束。

实例2:细粒度地控制在同一时间一个操作可以被并发得执行次数

下面代码并发的、不考虑顺序地给 NSArray 添加对象。

1
2
3
4
5
6
7
8
9
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

NSMutableArray *array = [[NSMutableArray alloc] init];

for (int i = 0; i < 10000; ++i) {
dispatch_async(queue, ^{
[array addObject:@(i)];
});
}

执行上面代码会有很大概率发生崩溃,因为并发的执行很容易发生内存错误。

这时可以用 Dispatch Semaphore 来细粒度地控制同一时间的并发执行次数。把上面代码改为以下代码即可避免发生崩溃。

1
2
3
4
5
6
7
8
9
10
11
12
13
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [[NSMutableArray alloc] init];

for (int i = 0; i < 10000; ++i) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:@(i)];
dispatch_semaphore_signal(semaphore);
});
}

上面代码中用 dispatch_semaphore_create(1) 创建了一个持有计数为 1 信号量 semaphore。然后把具体要限制并发次数的操作用一对 dispatch_semaphore_wait 和 dispatch_semaphore_signal 包含在其中。这样避免了 addObject 在同时多次并发执行时可能出现的问题。

dispatch_semaphore_wait 有返回值,上面代码也可以这么写,效果是一样的。

1
2
3
4
5
6
7
8
9
for (int i = 0; i < 10000; ++i) {
dispatch_async(queue, ^{
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (result == 0) {
[array addObject:@(i)];
dispatch_semaphore_signal(semaphore);
}
});
}