Dispatch Queue
苹果官方对 GCD 的说明:开发者要做的只是定义想执行的任务并追加到适当的 Dispatch Queue 中:
dispatch_async (queue, ^{
/*
* 执行的任务
*/
});
“Dispatch Queue” 就是执行处理的等待队列。通过 dispatch_async 函数等 API,在 Block 语法中记述想执行的处理并将其追加到 Dispatch Queue 中。Dispatch Queue 按照追加的顺序(先进先出 FIFO,First -In-First-Out)执行处理。
在执行处理时,存在两种 Dispatch Queue,一种是等待现在执行中处理的 Serial Dispatch Queue,另一种是不等待现在执行中处理的 Concurrent Dispatch Queue。
dispatch_queue_create
通过 GCD 的 API dispatch_queue_create 生成 Serial Dispatch Queue。
dispatch_queue_t mySerialDispatchQueue =
dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_queue_create 函数可任意生成多个 Dispatch Queue。如果将处理分别追加到多个 Serial Dispatch Queue 中,各个 Serial Dispatch Queue 将并行执行处理。但要注意:过多的使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能。
Serial Dispatch Queue 只在为了避免多线程编程问题之一时使用:数据竞争。
当想并行执行不发生数据竞争等问题的处理时,使用 Concurrent Dispatch Queue。由于 XNU 内核只使用有效管理的线程,因此不会发生大量开启 Serial Dispatch Queue 时引发的性能问题。
生成 Concurrent Dispatch Queue:
dispatch_queue_t myConcurrentDispatchQueue =
dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
Dispatch Queue 的名称推荐使用逆序全程域名 (FQDN,fully qualified domain name)。该名称将在 Xcode 的 Instruments 的调试器中作为 Dispatch Queue 名称表示,并且也会出现在 CrashLog 中,因此不建议设置为 NULL。
第二个参数指定为 NULL 时,生成 Serial Dispatch Queue;指定为 DISPATCH_QUEUE_CONCURRENT时,生成 Concurrent Dispatch Queue。
Dispatch Queue 必须手动释放,因为 Dispatch Queue 并没有像 Block 那样具有作为 Objective-C 对象来处理的技术:
dispatch_release(myConcurrentDispatchQueue);
相应的也存在 retain:
dispatch_retain(myConcurrentDispatchQueue);
即 Dispatch Queue 需要释放通过 dispatch_queue_create 函数生成并赋值给 dispatch_queue_t 变量 myConcurrentDispatchQueue 中的 Concurrent Dispatch Queue:
dispatch_queue_t myConcurrentDispatchQueue =
dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(myConcurrentDispatchQueue, ^{
NSLog(@"block on myConcurrentDispatchQueue");
});
dispatch_release(myConcurrentDispatchQueue);
此处注意:
- Block 持有 Dispatch Queue,dispatch_async 函数中 Block 通过 dispatch_retain 函数持有 Dispatch Queue;
- Dispatch Queue 由于被 Block 所持有,即使通过 dispatch_release 函数释放也不会被废弃;
- Block 执行结束后会释放 Dispatch Queue,此时谁都不持有 Dispatch Queue,Dispatch Queue 被废弃。
Main Dispatch Queue / Global Dispatch Queue
获取系统标准提供的 Dispatch Queue:Main Dispatch Queue 和 Global Dispatch Queue。Main Dispatch Queue 是在主线程中执行的 Dispatch Queue,属于 Serial Dispatch Queue。追加到 Main Dispatch Queue 的处理在主线程的 Runloop 中执行,因此要将用户界面的界面更新等必须在主线程中执行的处理追加到 Main Dispatch Queue 使用。(正好与 NSObject 类的 performSelectorOnMainThread 实例方法这一执行方法相同)
Global Dispatch Queue 是所有应用程序都能够使用的 Concurrent Dispatch Queue,不需要通过 dispatch_queue_create 函数逐个生成 Concurrent Dispatch Queue,获取 Global Dispatch Queue 使用即可。
Global Dispatch Queue 有 4 个执行优先级,分别是 High Priority、Default Priority、Low Priority 和 Background Priority。通过 XNU 内核用于 Global Dispatch Queue 的线程并不能保证实时性,执行优先级是大概的判断。
/*
* Main Dispatch Queue 获取方法
*/
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
/*
* Global Dispatch Queue (High Priority) 获取方法
*/
dispatch_queue_t globalDispatchQueueHigh =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
/*
* Global Dispatch Queue (Default Priority) 获取方法
*/
dispatch_queue_t globalDispatchQueueDefault =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* Global Dispatch Queue (Low Priority) 获取方法
*/
dispatch_queue_t globalDispatchQueueLow =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
/*
* Global Dispatch Queue (Background Priority) 获取方法
*/
dispatch_queue_t globalDispatchQueueBackground =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
/*
* 在默认优先级的 Global Dispatch Queue 中执行 Block
*/
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY, 0), ^{
/*
* 可并行执行的处理
*/
/*
* 在 Main Dispatch Queue 中执行 Block
*/
dispatch_async(dispatch_get_main_queue(), ^{
/*
* 只能在主线程中执行的处理
*/
});
});
dispatch_set_target_queue
dispatch_queue_create 生成的 Serial Dispatch Queue 和 Concurrent Dispatch Queue 都使用与默认优先级 Global Dispatch Queue 相同执行优先级的线程。
变更生成的 Dispatch Queue 的执行优先级要使用 dispatch_set_target_queue 函数:
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);
在必须将不可并行执行的处理追加到多个 Serial Dispatch Queue 中时,使用dispatch_set_target_queue 函数将目标指定为某一个 Serial Dispatch Queue,可防止并行执行。
dispatch_after
需要在指定时间后执行处理的情况,可使用 dispatch_after 函数实现:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"waited at least three seconds");
});
dispatch_after 并不是在指定时间后执行处理,而是在指定时间后追加处理到 Dispatch Queue 中等待执行,在希望大致延迟执行处理时非常有效。
dispatch_time 函数获取从 dispatch_time_t 类型值中指定的时间开始,到指定的毫微秒单位后的时间。DISPATCH_TIME_NOW 表示现在的时间:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
"ull" 是 C 语言的数值字面量,显式表明类型时使用的字符串 (unsigned long long)。数值和 NSEC_PER_SEC 的乘积得到单位为毫微秒的数值。使用 NSEC_PER_SEC 则可以以毫秒为单位计算:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);
dispatch_walltime 函数由 POSIX 中使用的 struct timespec 类型的时间得到 dispatch_time_t 类型的值。dispatch_time 函数通常用于计算相对时间,而 dispatch_walltime 函数用于计算绝对时间。
struct timespec 类型的时间可以很轻松地通过 NSDate 类对象做成:
dispatch_time_t getDispatchTimeByDate(NSDate *date)
{
NSTimeIntervel interval;
double second, subsecond;
struct timespec time;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
subsecond = mode(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
milestone = dispatch_walltime(&time, 0);
return milestone;
}
可由 NSDate 类对象获取能传递给 dispatch_after 函数的 dispatch_time_t 类型的值。
Dispatch Group
使用 Concurrent Dispatch Queue 或同时使用多个 Dispatch Queue 全部结束后想执行结束处理时,使用Dispatch Queue:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create;
dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});
dispatch_release(group);
在 Dispatch Group 中也可以使用 dispatch_group_wait 函数仅等待全部处理执行结束:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
DISPATCH_TIME_FOREVER 指定等待超时的时间,属于 dispatch_time_t 类型的值。这里 DISPATCH_TIME_FOREVER意味着永久等待,只要属于 Dispatch Group 的处理尚未结束,就会一直等待,中途不能取消。
也可以指定等待间隔为 1 秒:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
/*
* 属于 Dispatch Group 的全部处理执行结束
*/
} else {
/*
* 属于 Dispatch Group 的某一个处理还在执行中
*/
}
如果 dispatch_group_wait 函数的返回值不为 0,就意味着经过了指定的时间,但 Dispatch Group 的某一个处理还在执行。
指定 DISPATCH_TIME_NOW,则不用任何等待即可判定属于 Dispatch Group 的处理是否执行结束:
long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
在主线程的 Runloop 的每次循环中,可检查执行是否结束,从而不耗费多余的等待时间。仍然推荐使用 dispatch_group_notify 函数追加结束处理到 Main Dispatch Queue 中,因为 dispatch_group_notify 函数可以简化源代码。
dispatch_barrier_async
在访问数据库或文件时,写入处理不可与其他的写入处理以及包含读取处理的其他某些处理并行执行。但是读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。
为了高效率的进行访问,读取处理追加到 Concurrent Dispatch Queue 中,写入处理在任一个读取处理没有执行的状态下,追加到 Serial Dispatch Queue 中即可(在写入处理结束前,读取处理不可执行)。
虽然使用 Dispatch Group 和 dispatch_set_target_queue 函数也可以实现,但会比较复杂。GCD 提供了更为聪明的解决方法——dispatch_barrier_async 函数:
dispatch_queue_t queue =
dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_async(queue, blk2_for_reading);
dispatch_barrier_async(queue, blk3_for_writing);
dispatch_async(queue, blk4_for_reading);
dispatch_async(queue, blk5_for_reading);
dispatch_release(queue);
dispatch_barrier_async 函数会等待前面追加到 Concurrent Dispatch Queue 中的并行执行处理全部结束之后,再将指定的处理追加到该 Concurrent Dispatch Queue 中。
dispatch_sync
dispatch_async 函数意味着“非同步”(asynchronous),就是将指定的 Block “非同步”地追加到指定的 Dispatch Queue 中,dispatch_async 函数不做任何等待。
相应的,dispatch_sync 函数将指定的 Block “同步”地追加到指定的 Dispatch Queue 中,在追加 Block 结束之前,dispatch_sync 函数会一直等待:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_sync(queue, ^{/* 处理 */});
一旦调用 dispatch_sync 函数,在指定的处理执行结束之前,函数不会返回。dispatch_sync 函数可简化源代码,也可说是简易版的 dispatch_group_wait 函数。
但 dispatch_sync 函数容易发生死锁:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{NSLog(@"Hello?");});
在 Main Dispatch Queue 即主线程中执行指定的 Block,并等待其执行结束。但其实主线程中正在执行这些代码,所以无法追加执行到 Main Dispatch Queue 的 Block:
dispatch_queue_t queue = dispatch_async(queue, ^{
dispatch_sync(queue, ^{NSLog(@"Hello");});
});
当然 Serial Dispatch Queue 也会引起相同的问题:
dispatch_queue_t queue =
dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{NSLog(@"Hello?");});
});
dispatch_release(queue);
dispatch_apply
dispatch_apply 函数是 dispatch_sync 函数和 Dispatch Group 的关联API。该函数按指定的次数将指定的 Block 追加到指定的 Dispatch Queue 中,并等待全部处理执行结束:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
NSLog(@"done");
该源代码执行结果为:
4
1
0
3
5
2
6
8
9
7
done
因为在 Global Dispatch Queue 中执行处理,所以各个处理的执行时间不定,但是最后的 done 必定在最后的位置上。因为 dispatch_apply 函数会等待全部处理执行结束。
size_t index 为了重复追加 Block 并区分各个 Block 而使用:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index){
NSLog(@"%zu: %@", index, [array objectAtIndex:index);
});
由于 dispatch_apply 函数也与 dispatch_sync 函数相同,会等待处理执行结束,因此推荐在 dispatch_async 函数中非同步地执行 dispatch_apply 函数:
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* 在 Global Dispatch Queue 中非同步执行
*/
dispatch_async(queue, ^{
/*
* Global Dispatch Queue
* 等待 dispatch_apply 函数中全部处理执行结束
*/
dispatch_apply([array count], queue, ^(size_t index) {
/*
* 并行处理包含在 NSArray 对象的全部对象
*/
NSLog(@"%zu: %@, index, [array objectAtIndex:index]");
});
/*
* dispatch_apply 函数中的处理全部执行结束
*/
/*
* 在 Main Dispatch Queue 中非同步执行
*/
dispatch_async(dispatch_get_main_queue(), ^{
/*
* 在 Main Dispatch Queue 中执行处理
* 用户界面更新等
*/
NSLog(@“done”);
});
});
dispatch_suspend / dispatch_resume
当追加大量处理到 Dispatch Queue 时,在追加处理的过程中,有时希望不执行已追加的处理,则将 Dispatch Queue 挂起,当可以执行时再恢复:
dispatch_suspend(queue);
dispatch_resume 函数恢复指定的 Dispatch Queue:
dispatch_resume(queue);
Dispatch Semaphore
比使用 Serial Dispatch Queue 和 dispatch_barrier_async 函数更细粒度的排他控制。
Dispatch Semaphore 是持有计数的信号,是多线程编程中的计数类型信号。计数为 0 时等待,计数为 1 或大于 1 时,减去 1 而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
参数表示计数的初始值。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait 函数等待 Dispatch Semaphore 的计数值达到大于或等于 1。当计数值大于等于 1,或者在待机中计数值大于等于 1 时,对该计数进行减法并从 dispatch_semaphore_wait 函数返回。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0) {
/*
* 由于 Dispatch Semaphore 的计数值达到大于等于 1
* 或者在待机中的指定时间内
* Dispatch Semaphore 的计数值达到大于等于 1
* 所以 Dispatch Semaphore 的计数值减去 1
*
* 可执行需要进行排他控制的处理
*/
} else {
/*
* 由于 Dispatch Semaphore 的计数值为 0
* 因此在达到指定时间为止待机
*/
}
dispatch_semaphore_wait 函数返回 0 时,可安全地执行需要进行排他控制的处理。该处理结束时通过 dispatch_semaphore_signal 函数将 Dispatch Semaphore 的计数值加 1。
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
* 生成 Dispatch Semaphore。
*
* Dispatch Semaphore 的计数初始值设定为 “1”。
*
* 保证可访问 NSMutableArray 类对象的线程
* 同时只能有 1 个。
*/
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。
*
* 一直等待,直到 Dispatch Semaphore 的计数值达到大于等于 1。
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
/*
* 由于 Dispatch Semaphore 的计数值达到大于等于 1
* 所以将 Dispatch Semaphore 的计数值减去 1,
* dispatch_semaphore_wait 函数执行返回。
*
* 即执行到此时的
* Dispatch Semaphore 的计数值恒为 "0"。
*
* 由于可访问 NSMutableArray 类对象的线程
* 只有 1 个
* 因此可安全地进行更新
*/
[array addObject:[NSNumber numberWithInt:i]];
/*
* 排他控制处理结束
* 所以通过 dispatch_semaphore_signal 函数
* 将 Dispatch Semaphore 的计数值加 1。
*
* 如果有通过 dispatch_semaphore_wait 函数
* 等待 Dispatch Semaphore 的计数值增加的线程
* 就由最先等待的线程执行
*/
dispatch_semaphore_signal(semaphore);
});
}
/*
* 如果使用结束,需要如以下这样
* 释放 Dispatch Semaphore
*
* dispatch_release(semaphore);
*/
dispatch_once
dispatch_once 函数是保证在应用程序执行中只执行一次指定处理的 API。
static int initialized = NO;
if (initialized == NO) {
/*
* 初始化
*/
initialized = YES;
}
如果使用 dispatch_once 函数:
static dispatch_once_t pred;
dispatch_once(&pred, ^{
/*
* 初始化
*/
});
通过 dispatch_once 函数,该源代码在多线程环境下执行也可保证百分之百安全。这就是所说的单例模式,在生成单例对象时使用。
Dispatch I/O
在读取较大文件时,如果将文件分成合适的大小并使用 Dispatch I/O 和 Dispatch Data 就能一次使用多个线程更快的读取了。
通过 Dispatch I/O 读写文件时,使用 Global Dispatch Queue 将 1 个文件按某个大小 read/write。
dispatch_async(queue, ^{/*读取 0 ~ 8191 字节 */});
dispatch_async(queue, ^{/*读取 8192 ~ 16383 字节 */});
dispatch_async(queue, ^{/*读取 16384 ~ 24575 字节 */});
可像上面这样,将文件分割为一块一块的进行读取处理。分割读取的数据通过使用 Dispatch Data 可更为简单地进行结合和分割:
pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err) {
close(fd);
});
*out_fd = repair[1];
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q,
^(bool done, dispatch_data_t pipe data, int err) {
if (err == 0)
{
size_t len = dispatch_data_get_size(pipedata);
if (len > 0)
{
const char *bytes = NULL;
char *encoded;
dispatch_data_t md = dispatch_data_create_map(
pipedata, (const void **)&bytes, &len);
encoded = asl_core_encode_buffer(bytes, len);
asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
free(encoded);
_asl_send_message(NULL, merged_msg, -1, NULL);
asl_msg_release(merged_msg);
dispatch_release(md);
}
}
if (done)
{
dispatch_semaphore_signal(sem);
dispatch_release(pipe_channel);
dispatch_release(pipe_q);
}
});
以上摘自 Apple System Log API 用的源代码(Libc-763.11 gen/asl.c)。dispatch_io_create 函数生成 Dispatch I/O,并指定发生错误时用来执行处理的 Block,以及执行该 Block 的 Dispatch Queue。dispatch_io_set_low_water 函数设定一次读取的大小(分割大小),dispatch_io_read 函数使用 Global Dispatch Queue 并列读取。每当各个分割的文件块读取结束时,将含有文件块数据的 Dispatch Data 传递给 dispatch_io_read 函数指定的读取结束时回调用的 Block。回调用的 Block 分析传递过来的 Dispatch Data 并进行结合处理。
如果想提高文件读取速度,可以尝试使用 Dispatch I/O。