- (1)Dispatch Queue
- (2)dispatch_queue_create
- (3) Main Dispatch Queue / Global Dispatch Queue
- (4)dispatch_set_target_queue
- (5)dispatch_after
- (6)Dispatch Group
- (7)dispatch_barrier_async
- (8)dispatch_sync
- (9)dispatch_apply
- (10)dispatch_suspend / dispatch_resume
- (11)Dispatch Semephore
- (12)dispatch_once
- (13)Dispatch I/O
- (14)Dispatch Source
Grand Central Dispatch是异步执行任务的技术
在GCD之前,Cocoa框架提供处理异步执行任务的API
performSelectorInBackground: withObject: //后台处理任务 performSelectorOnMainThread:withObject: waitUntilDone: //回到主线程处理任务
(1)Dispatch Queue
开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
Dispatch Queue是执行处理的等待队列,按照FIFO的执行处理。
两种不同的执行队列:
Dispatch Queue种类 | 说明 |
---|---|
Serial Dispatch Queue | 等待现在执行中处理结束 (串行队列) |
Concurrent Dispatch Queue | 不等待现在执行中处理结束(并行队列) |
(2)dispatch_queue_create
dispatch_queue_create函数用于生成Dispatch Queue
dispatch_queue_t queue = dispatch_queue_create("com.name", DISPATCH_QUEUE_SERIAL);
两个参数:
- 第一个参数指定Queue的名称。推荐使用逆序全程域名
- 第二个参数指定Queue的类型,NULL或DISPATCH_QUEUE_SERIAL都是表示Serial Dispatch Queue,指定为DISPATCH_QUEUE_CONCURRENT表示Concurrent Dispatch Queue
dispatch_release函数释放创建的队列,对应的也就有dispatch_retain函数来持有队列。
(3) Main Dispatch Queue / Global Dispatch Queue
Main Dispatch Queue 是Serial Dispatch Queue,因为主线程只有一个。添加到Main Dispatch Queue的处理在主线程的Runloop中执行。
Global Dispatch Queue是Concurrent Dispatch Queue。Global Dispatch Queue有4个执行优先级,分别是高优先级,默认优先级,低优先级,后台优先级。但是通过XNU内核用于Global Dispatch Queue的线程并不能保证实时性,因此执行的优先级知识大致的判断。
名称 | 种类 | 说明 |
---|---|---|
Main Dispatch Queue | Serial Dispatch Queue | 主线程执行 |
Global Dispatch Queue(High Priority) | Concurrent Dispatch Queue | 高优先级 |
Global Dispatch Queue(Default Priority) | Concurrent Dispatch Queue | 默认 |
Global Dispatch Queue(Low Priority) | Concurrent Dispatch Queue | 低优先级 |
Global Dispatch Queue(Background Priority) | Concurrent Dispatch Queue | 后台 |
获取全局队列,第一个参数是优先级,第二个参数作为保留字段,一般为0
// 获取全局队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
// 获取主队列
dispatch_get_main_queue()
参数类型 | 说明 |
---|---|
DISPATCH_QUEUE_PRIORITY_HIGH | 高优先级 |
DISPATCH_QUEUE_PRIORITY_DEFAULT | 默认 |
DISPATCH_QUEUE_PRIORITY_LOW | 低优先级 |
DISPATCH_QUEUE_PRIORITY_BACKGROUND | 后台执行 |
注意⚠️:
Main Dispatch Queue和Global Dispatch Queue中执行dispatch_release函数和dispatch_retain函数不会引起任何变化
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//全局队列
NSLog(@"currentThread---%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
// 主队列
NSLog(@"currentThread---%@",[NSThread currentThread]);
});
});
(4)dispatch_set_target_queue
dispatch_set_target_queue函数主要作用是变更Dispatch Queue的执行优先级。
dispatch_queue_t serialQueue = dispatch_queue_create("com.baidu.xxx", NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 第一个参数是要变更执行优先级的Queue
// 第二个参数是指定执行优先级的目标
dispatch_set_target_queue(serialQueue, globalQueue);
第一个参数不能是系统指定的Main Dispatch Queue和Global Dispatch Queue,因为不确定最终发生了什么。
如果在多个Serial Dispatch Queue中使用dispatch_set_target_queue函数指定目标为某一个Serial Dispatch Queue,那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能执行一个处理。所以dispatch_set_target_queue函数可以防止多个并行执行的Serial Dispatch Queue按照目标一次执行。
(5)dispatch_after
dispatch_after并不是指定时间后执行处理,而是在指定时间追加处理到队列中。
// 延迟3秒添加到队列中
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue() , ^{
NSLog(@"3s after");
});
上述代码,是将block处理在3秒后添加到主队列中,因为主队列是在Runloop中执行的,所以每隔1/60执行一次runloop,所以Block最快在3秒后执行,最慢在3+1/60秒之后执行。
dispatch_after函数参数说明:
- 第一个参数是指定时间。这个值可以由dispatch_time和dispatch_walltime函数生成。
- 第二个参数是需要追加到的队列
- 第三个参数block是需要执行的处理
dispatch_time函数通常用于计算相对时间。
dispatch_walltime函数用于计算绝对时间,根据系统时钟创建绝对时间。
// 第一个参数是一个时间结构体,如果传入NULL,方法会默认使用当前时间
// 第二个参数是单位是纳秒
dispatch_time_t wtime = dispatch_walltime(NULL, 1000 * 1000 * 1000)
(6)Dispatch Group
Dispatch Group可以监视加入的所有执行队列结束。一旦检测到所有任务执行结束,就可将结束的任务追加到Dispatch Queue中。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"执行任务A");
});
dispatch_group_async(group, queue, ^{
NSLog(@"执行任务B");
});
dispatch_group_async(group, queue, ^{
NSLog(@"执行任务C");
});
dispatch_group_async(group, queue, ^{
NSLog(@"执行任务D");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"最后执行");
});
// 第一个参数是group
// 第二个参数是等待时间(超时),DISPATCH_TIME_FOREVER代表永不超时
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group 执行结束");
最后执行结果:
2021-02-23 16:57:19.582588+0800 GCD演示[11718:280292] 执行任务B 2021-02-23 16:57:19.582620+0800 GCD演示[11718:280297] 执行任务C 2021-02-23 16:57:19.582617+0800 GCD演示[11718:280290] 执行任务D 2021-02-23 16:57:19.582584+0800 GCD演示[11718:280291] 执行任务A 2021-02-23 16:57:19.582772+0800 GCD演示[11718:280297] 最后执行 2021-02-23 16:57:19.582781+0800 GCD演示[11718:280220] group 执行结束
dispatch_group_async函数与dispatch_async函数相同。
dispatch_group_notify函数会在group中所有执行队列完成之后追加到最后队列中。不管该函数指定的Dispatch Queue是否是哪个,只要属于Dispatch Group中的所有队列都执行结束在追加该函数的Block到队列时。
dispatch_group_wait函数等待全部处理执行结束。如果返回值不为0,则在指定时间之后,还存在队列正在执行,如果返回值为0,则代表执行队列已经全部执行结束。
(7)dispatch_barrier_async
使用dispatch_barrier_async同新创建的Concurrent Dispatch Queue一起使用,可以实现高效率的数据库访问和文件访问。
dispatch_barrier_async函数能避免数据竞争问题
dispatch_queue_t queue = dispatch_queue_create("com.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"执行任务A");
});
dispatch_async(queue, ^{
NSLog(@"执行任务B");
});
// 该函数会在任务A和任务B执行完成之后再执行
// 并且该函数执行完成之后才会执行后面的任务
dispatch_barrier_async(queue, ^{
NSLog(@"barrier执行任务");
});
dispatch_async(queue, ^{
NSLog(@"执行任务C");
});
dispatch_async(queue, ^{
NSLog(@"执行任务D");
});
dispatch_async(queue, ^{
NSLog(@"执行任务E");
});
输出结果:
2021-02-23 17:22:31.147158+0800 GCD演示[12304:298523] 执行任务A 2021-02-23 17:22:31.147169+0800 GCD演示[12304:298521] 执行任务B 2021-02-23 17:22:31.147309+0800 GCD演示[12304:298521] barrier执行任务 2021-02-23 17:22:31.147420+0800 GCD演示[12304:298521] 执行任务C 2021-02-23 17:22:31.147423+0800 GCD演示[12304:298523] 执行任务D 2021-02-23 17:22:31.147434+0800 GCD演示[12304:298529] 执行任务E
(8)dispatch_sync
dispatch_sync同步函数。
dispatch_sync使用主队列会造成死锁问题。同样的,与串行队列一起使用也会造成死锁问题。
下面几种死锁的情况:
// 情况一
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"AAAA");
});
// 情况二
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"BBB");
});
});
// 情况三
dispatch_queue_t queue = dispatch_queue_create("com.xxx", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"BBB");
});
});
(9)dispatch_apply
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的执行任务追加到Dispatch Queue队列中,并等待全部处理执行结束。
dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^(size_t index){
NSLog(@"%d",index);
});
dispatch_apply参数说明:
- 参数一:执行重复次数
- 参数二:追加任务的执行队列
- 参数三:执行任务处理的Block
(10)dispatch_suspend / dispatch_resume
dispatch_suspend函数:挂起指定的队列。
dispatch_resume函数:恢复挂起的指定队列。
这两个函数对已经执行的队列没有任何的影响。挂起尚未执行的处理将停止,在恢复之后才可以继续执行。
(11)Dispatch Semephore
Dispatch Semephore是GCD中的信号量。是持有计数的信号,Semephore使用计数来实现该功能,计数为0时等待,计数为1或大于1时,减去1而不等待。
核心方法:
dispatch_semaphore_create(long value)
: 创建一个信号量dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)
:接收一个信号和等待时间,若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者超过设定时间值;若信号量大于0,则会使信号量减1并返回,程序继续住下执行。dispatch_semaphore_signal(dispatch_semaphore_t dsema)
: 使信号量加1并返回
线程同步
利用信号量的特性,先创建信号量为0 在需要保持同步的地方,使用dispatch_semaphore_wait等待线程完成 在子线程完成之后调用
dispatch_semaphore_signal
去触发等待往下执行
示例代码
// 保持线程同步
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); // 关键,设置信号量为0
// 该函数等待semaphore的计数大于或等于1
__block int a = 10;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
a = 20;
// 触发信号+1
dispatch_semaphore_signal(semaphore);
});
long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
if (result == 0) {
// 成功等待返回
NSLog(@"%zd",a);
} else {
// 超过设定时长
}
打印结果是 20,所以到result之后,需要等着子线程执行完成之后才往下走。
线程加锁
- 创建信号量为1的信号
- 在需要加锁的地方之前调用dispatch_semaphore_wait
- 在需要解锁的地方使用dispatch_semaphore_signal
示例代码:
// 线程加锁
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
__block int a = 100;
for (int i = 0; i < 100; i ++) {
// for循环用于开辟多个线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (a > 0) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 相当于加锁
a -= 1;
NSLog(@"%zd",a);
dispatch_semaphore_signal(semaphore); //相当于解锁
}
});
}
(12)dispatch_once
dispatch_once函数保证在应用程序执行中只执行一次指定处理。
一般常用于单利模式
+ (instancetype)shared {
static NSObject *instance;
static dispatch_once_t once;
dispatch_once(&once, ^{
instance = [[NSObject alloc] init];
});
return instance;
}
(13)Dispatch I/O
dispatch_io_create 函数生成Dispatch I/O,并指定发生error时用来执行处理的block,以及执行该block的Dispatch Queue。
dispatch_io_set_low_water 函数设置一次读取的大小
dispatch_io_read 函数使用Global Dispatch Queue 开始并发读取。每当各个分割的文件块读取结束时,将含有文件块数据的 Dispatch Data(这里指pipedata) 传递给 “ dispatch_io_read 函数指定的读取结束时回调用的block ”,这个block拿到每一块读取好的 Dispatch Data(这里指pipe data),然后进行合并处理。
如果想提高文件读取速度,可以尝试使用 Dispatch I/O.
以下源码来源:https://opensource.apple.com/source/Libc/Libc-763.11/gen/asl.c
static int
_asl_auxiliary(aslmsg msg, const char *title, const char *uti, const char *url, int *out_fd)
{
asl_msg_t *merged_msg;
asl_msg_aux_t aux;
asl_msg_aux_0_t aux0;
fileport_t fileport;
kern_return_t kstatus;
uint32_t outlen, newurllen, len, where;
int status, fd, fdpair[2];
caddr_t out, newurl;
dispatch_queue_t pipe_q;
dispatch_io_t pipe_channel;
dispatch_semaphore_t sem;
/* ..... 此处省略若干代码.....*/
// 创建串行队列
pipe_q = dispatch_queue_create("PipeQ", NULL);
// 创建 Dispatch I/O
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
close(fd);
});
*out_fd = fdpair[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 pipedata, int err){
if (err == 0) // err等于0 说明读取无误
{
// 读取完“单个文件块”的大小
size_t len = dispatch_data_get_size(pipedata);
if (len > 0)
{
// 定义一个字节数组bytes
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);
}
});
}
(14)Dispatch Source
dispatch source是基础数据类型,协调特定底层系统事件的处理
Dispatch source替代了异步回调函数,来处理系统相关的事件。
当你配置一个dispatch source时,你指定要监测的事件、dispatch queue、以及处理事件的代码(block或函数)。
当事件发生时,dispatch source会提交你的block或函数到指定的queue去执行和手工提交到queue的任务不同,dispatch source为应用提供连续的事件源。除非你显式地取消,dispatch source会一直保留与dispatch queue的关联。只要相应的事件发生,就会提交关联的代码到dispatch queue去执行。为了防止事件积压到dispatch queue,dispatch source实现了事件合并机制。 如果新事件在上一个事件处理器出列并执行之前到达,dispatch source会将新旧事件的数据合并。 根据事件类型的不同,合并操作可能会替换旧事件,或者更新旧事件的信息。
Dispatch是BSD系内核惯有功能kqueue的包装。kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。其CPU负荷非常小,尽量不占用资源。kqueue可以说是应用程序处理XUN内核中发生的各种事件的方法中最优秀的一种。
Dispatch Source的种类
名称 | 内容 |
---|---|
DISPATCH_SOURCE_TYPE_DATA_ADD | 数据增加 |
DISPATCH_SOURCE_TYPE_DATA_OR | 获取的内容进行OR运算 |
DISPATCH_SOURCE_TYPE_DATA_REPLACE | 获取的内容替换 |
DISPATCH_SOURCE_TYPE_MACH_SEND | MACH端口发送 |
DISPATCH_SOURCE_TYPE_MACH_RECV | MACH端口接受 |
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE | 内存压力 |
DISPATCH_SOURCE_TYPE_PROC | 检测到与进程相关的事件 |
DISPATCH_SOURCE_TYPE_READ | 可读取文件映像 |
DISPATCH_SOURCE_TYPE_SIGNAL | 接受信号 |
DISPATCH_SOURCE_TYPE_TIMER | 定时器 |
DISPATCH_SOURCE_TYPE_VNODE | 文件系统有变更 |
DISPATCH_SOURCE_TYPE_WRITE | 可写入文件映像 |
DISPATCH_MACH_SEND_DEAD |
自定义定时器
__block int a = 0;
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 设定 10秒间隔,允许1秒延迟
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
// 定时任务
NSLog(@"event");
a = a + 1;
if (a > 10) { //满足某一条件之后,取消定时器
// 取消Dispatch Source
dispatch_source_cancel(timer);
}
});
// 指定取消定时器的任务处理
dispatch_source_set_cancel_handler(timer, ^{
NSLog(@"cancel timer");
});
// 启动定时器
dispatch_resume(timer);