学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

GCD之 Barrier,Apply,Source

Dispatch Barrier栅栏函数

简介

在并发调度队列中执行的任务的同步点

使用栅栏函数来同步调度队列中一个或多个任务的执行。在向并发调度队列添加栅栏时,该队列会延迟栅栏任务(以及栅栏之后提交的所有任务)的执行,直到所有先前提交的任务都执行完成为止。在完成先前的任务后,队列将自己执行栅栏任务。栅栏任务执行完毕之后,队列将恢复其正常执行行为。

栅栏函数

  • 同步栅栏函数
    dispatch_barrier_sync:提交一个栅栏 block 以同步执行,并等待该 block 执行完;
    dispatch_barrier_sync_f:提交一个栅栏 function 以同步执行,并等待该 function 执行完。

  • 异步栅栏函数
    dispatch_barrier_async:提交一个栅栏 block 以异步执行,并直接返回;
    dispatch_barrier_async_f:提交一个栅栏 function 以异步执行,并直接返回。

同步栅栏函数 dispatch_barrier_sync

1
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);

dispatch_barrier_sync同步栅栏函数会等待该函数传入的队列中的任务都执行完毕,再执行dispatch_barrier_sync函数中的任务以及后面的任务,会阻塞当前线程。

  • 使用场景:保证顺序执行,会阻塞当前线程

需求: 现有任务1、2、3、4,前两个任务执行完毕,再执行后两个任务以及主线程的代码。

解决方法:

  1. 使用 GCD 队列组;
  2. 使用dispatch_barrier_sync函数。
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
/* 1.异步函数 */
dispatch_async(queue, ^{
for (NSUInteger i = 0; i < 3; i++) {
NSLog(@"执行任务1-%zd-%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSUInteger i = 0; i < 3; i++) {
NSLog(@"执行任务2-%zd-%@",i,[NSThread currentThread]);
}
});
/* 2. 同步栅栏函数 */
dispatch_barrier_sync(queue, ^{
NSLog(@"------------------dispatch_barrier_sync-%@",[NSThread currentThread]);
});
NSLog(@"任务1、2执行完毕");
/* 3. 异步函数 */
dispatch_async(queue, ^{
for (NSUInteger i = 0; i < 3; i++) {
NSLog(@"执行任务3-%zd-%@",i,[NSThread currentThread]);
}
});
NSLog(@"正在执行任务3、4");
dispatch_async(queue, ^{
for (NSUInteger i = 0; i < 3; i++) {
NSLog(@"执行任务4-%zd-%@",i,[NSThread currentThread]);
}
});
/*
执行任务1-0-<NSThread: 0x600003ffa780>{number = 5, name = (null)}
执行任务2-0-<NSThread: 0x600003fefa00>{number = 3, name = (null)}
执行任务2-1-<NSThread: 0x600003fefa00>{number = 3, name = (null)}
执行任务1-1-<NSThread: 0x600003ffa780>{number = 5, name = (null)}
执行任务2-2-<NSThread: 0x600003fefa00>{number = 3, name = (null)}
执行任务1-2-<NSThread: 0x600003ffa780>{number = 5, name = (null)}
------------------dispatch_barrier_sync-<NSThread: 0x600003fa5b80>{number = 1, name = main}
任务1、2执行完毕
正在执行任务3、4
执行任务3-0-<NSThread: 0x600003ffa780>{number = 5, name = (null)}
执行任务4-0-<NSThread: 0x600003fefa00>{number = 3, name = (null)}
执行任务3-1-<NSThread: 0x600003ffa780>{number = 5, name = (null)}
执行任务4-1-<NSThread: 0x600003fefa00>{number = 3, name = (null)}
执行任务3-2-<NSThread: 0x600003ffa780>{number = 5, name = (null)}
执行任务4-2-<NSThread: 0x600003fefa00>{number = 3, name = (null)}
*/

异步栅栏函数 dispatch_barrier_async

1
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

先来看一下上面的代码改为异步栅栏函数的执行效果:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
/* 1.异步函数 */
dispatch_async(queue, ^{
for (NSUInteger i = 0; i < 3; i++) {
NSLog(@"执行任务1-%zd-%@",i,[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSUInteger i = 0; i < 3; i++) {
NSLog(@"执行任务2-%zd-%@",i,[NSThread currentThread]);
}
});
/* 2. 异步栅栏函数 */
dispatch_barrier_async(queue, ^{
NSLog(@"------------------dispatch_barrier_async-%@",[NSThread currentThread]);
});
NSLog(@"任务1、2执行完毕");
/* 3. 异步函数 */
dispatch_async(queue, ^{
for (NSUInteger i = 0; i < 3; i++) {
NSLog(@"执行任务3-%zd-%@",i,[NSThread currentThread]);
}
});
NSLog(@"正在执行任务3、4");
dispatch_async(queue, ^{
for (NSUInteger i = 0; i < 3; i++) {
NSLog(@"执行任务4-%zd-%@",i,[NSThread currentThread]);
}
});
/*
任务1、2执行完毕
执行任务2-0-<NSThread: 0x6000020f3100>{number = 4, name = (null)}
正在执行任务3、4
执行任务1-0-<NSThread: 0x6000020c6a00>{number = 6, name = (null)}
执行任务2-1-<NSThread: 0x6000020f3100>{number = 4, name = (null)}
执行任务1-1-<NSThread: 0x6000020c6a00>{number = 6, name = (null)}
执行任务2-2-<NSThread: 0x6000020f3100>{number = 4, name = (null)}
执行任务1-2-<NSThread: 0x6000020c6a00>{number = 6, name = (null)}
------------------dispatch_barrier_async-<NSThread: 0x6000020c6a00>{number = 6, name = (null)}
执行任务4-0-<NSThread: 0x6000020d2e00>{number = 7, name = (null)}
执行任务3-0-<NSThread: 0x6000020c6a00>{number = 6, name = (null)}
执行任务4-1-<NSThread: 0x6000020d2e00>{number = 7, name = (null)}
执行任务3-1-<NSThread: 0x6000020c6a00>{number = 6, name = (null)}
执行任务4-2-<NSThread: 0x6000020d2e00>{number = 7, name = (null)}
执行任务3-2-<NSThread: 0x6000020c6a00>{number = 6, name = (null)}
*/

从打印日志可以看到,改为异步栅栏函数,任务3、4仍然可以等到任务1、2以及栅栏任务都执行完毕再执行,但不会阻塞主线程,并且栅栏任务是在子线程执行。

dispatch_barrier_async异步栅栏函数会等待该函数传入的队列中的任务都执行完毕,再执行dispatch_barrier_async函数中的任务以及后面的任务,执行该函数会直接返回,继续往下执行代码,不会阻塞当前线程。

  • 主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新;
  • 适合于大规模的 I/O 操作;
  • 当访问数据库或者文件的时候,更新数据的时候不能和其他更新或者读取的操作在同一时间执行,可以使用队列组不过有点复杂,可以使用dispatch_barrier_async解决;
  • 保证线程安全、读写安全。

使用场景1:保证线程安全

例如: 我们要从网络上异步获取很多图片,然后将它们添加到非线程安全的对象——数组中去:异步并发。
  同一时间点,可能有多个线程执行给数组添加对象的方法,所以可能会丢掉 1 到多次,我们执行 1000 次,可能数组就保存了 990 多个,还有程序出现奔溃的可能。

解决办法:
① 加锁:比较耗时,而且下载完什么时候添加进数组也不一定。我们希望所有图片都下载完,再往数组里面添加;
② 使用 GCD 队列组;
③ 使用dispatch_barrier_async函数,栅栏中的任务会等待队列中的所有任务执行完成,才会执行栅栏中的任务,保证了线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 5; i++) {
dispatch_async(queue, ^{
//下载图片
NSLog(@"图片下载完成%d,%@",i,[NSThread currentThread]);
dispatch_barrier_async(queue, ^{
//将图片添加进数组
NSLog(@"添加图片%d,%@",i,[NSThread currentThread]);
});
});
}
/*
图片下载完成2,<NSThread: 0x600000348d80>{number = 5, name = (null)}
图片下载完成0,<NSThread: 0x600000341f40>{number = 4, name = (null)}
图片下载完成1,<NSThread: 0x60000036f480>{number = 6, name = (null)}
图片下载完成3,<NSThread: 0x60000039ce80>{number = 7, name = (null)}
图片下载完成4,<NSThread: 0x600000348d80>{number = 5, name = (null)}
添加图片2,<NSThread: 0x600000348d80>{number = 5, name = (null)}
添加图片0,<NSThread: 0x600000348d80>{number = 5, name = (null)}
添加图片1,<NSThread: 0x600000348d80>{number = 5, name = (null)}
添加图片3,<NSThread: 0x600000348d80>{number = 5, name = (null)}
添加图片4,<NSThread: 0x600000348d80>{number = 5, name = (null)}
*/

使用场景2:实现读写安全

dispatch_barrier_async可以用来实现“读写安全”。我们将“写”操作放在dispatch_barrier_async中,这样能确保在“写”操作的时候会等待前面的“读”操作完成,而后续的“读”操作也会等到“写”操作完成后才能继续执行,提高文件读写的执行效率。

1
2
3
4
5
6
7
8
9
10
// 创建一个并发队列
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
// 读:执行“读”操作
dispatch_async(queue, ^{

});
// 写:执行“写”操作
dispatch_barrier_async(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
28
29
30
31
32
33
34
35
36
37
38
39
- (void)viewDidLoad {
[super viewDidLoad];
self.queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 3; i++) {
[test read];
[test read];
[test write];
[test read];
}
}

- (void)read {
dispatch_async(self.queue, ^{
sleep(1);
NSLog(@"read,%@",[NSThread currentThread]);
});
}

- (void)write {
dispatch_barrier_async(self.queue, ^{
sleep(1);
NSLog(@"write,%@",[NSThread currentThread]);
});
}

/*
2020-01-20 01:45:09.847878+0800 多线程[27767:6103230] read,<NSThread: 0x600002d42ac0>{number = 7, name = (null)}
2020-01-20 01:45:09.847849+0800 多线程[27767:6096149] read,<NSThread: 0x600002d8ed80>{number = 4, name = (null)}
2020-01-20 01:45:10.849965+0800 多线程[27767:6096149] write,<NSThread: 0x600002d8ed80>{number = 4, name = (null)}
2020-01-20 01:45:11.851259+0800 多线程[27767:6103230] read,<NSThread: 0x600002d42ac0>{number = 7, name = (null)}
2020-01-20 01:45:11.851265+0800 多线程[27767:6103231] read,<NSThread: 0x600002d42640>{number = 8, name = (null)}
2020-01-20 01:45:11.851277+0800 多线程[27767:6096149] read,<NSThread: 0x600002d8ed80>{number = 4, name = (null)}
2020-01-20 01:45:12.854305+0800 多线程[27767:6103231] write,<NSThread: 0x600002d42640>{number = 8, name = (null)}
2020-01-20 01:45:13.859167+0800 多线程[27767:6103231] read,<NSThread: 0x600002d42640>{number = 8, name = (null)}
2020-01-20 01:45:13.859167+0800 多线程[27767:6103230] read,<NSThread: 0x600002d42ac0>{number = 7, name = (null)}
2020-01-20 01:45:13.859167+0800 多线程[27767:6096149] read,<NSThread: 0x600002d8ed80>{number = 4, name = (null)}
2020-01-20 01:45:14.864153+0800 多线程[27767:6103231] write,<NSThread: 0x600002d42640>{number = 8, name = (null)}
2020-01-20 01:45:15.869272+0800 多线程[27767:6096149] read,<NSThread: 0x600002d8ed80>{number = 4, name = (null)}
*/

dispatch_barrier_syncdispatch_barrier_async 的区别及注意点

相同点: 都会等待队列中在它们(栅栏)之前提交的任务都执行完毕,再执行它们的任务,接着执行它们后面的任务。

不同点:

  • dispatch_barrier_sync:提交一个栅栏 block 以同步执行,并等待该 block 执行完;由于是同步,不会开启新的子线程,会阻塞当前线程。
  • dispatch_barrier_async:提交一个栅栏 block 以异步执行,并直接返回,会继续往下执行代码,不会阻塞当前线程。

注意点:

  • dispatch_barrier_(a)sync函数传入的的队列必须是自己手动创建的并发队列,如果传入的是全局并发队列或者串行队列,那么这个函数是没有栅栏的效果的,效果等同于dispatch_(a)sync函数。
  • 只能栅栏dispatch_barrier_(a)sync函数中传入的queue。

Dispatch Apply多次执行

dispatch_apply

类似一个 for 循环。提交一个 block 到指定队列,并使该 block 执行指定的次数 n,等待 block 执行完 n 次才会返回。

1
2
3
4
5
6
7
8
9
10
11
12
/*!
* @param iterations
* 执行 block 的次数
*
* @param queue
*
* @param block
* 要提交的 block,此参数不能为空(NULL)
* 该 block 没有返回值,并带有一个 size_t 类型的参数(iteration:当前迭代索引,即当前是第几次调用)
*/

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

注意点:

  • 该函数会等待 block 都执行完毕才会返回,所以是同步的,会阻塞;
  • 如果指定的队列是全局并发队列dispatch_get_global_queue,则这些 block 可以并发执行,这里需要注意可重入性;
  • 如果指定的队列是手动创建的并发队列,在有些情况下不会并发执行,所以建议使用全局并发队列dispatch_get_global_queue
  • 当使用串行队列时,不会开启子线程,block 在主线程按串行执行;
  • 当使用并发队列时,不一定会开启子线程,block 不一定都在子线程执行,也可能都在主线程执行,取决于任务的耗时程度。

串行队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)test
{
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"开始执行");
dispatch_apply(5, queue, ^(size_t i) {
sleep(1);
NSLog(@"%zu,%@",i,[NSThread currentThread]);
});
NSLog(@"执行完毕");
}
/*
2020-02-01 00:20:00.828203+0800 多线程[5402:867594] 开始执行
2020-02-01 00:20:01.829521+0800 多线程[5402:867594] 0,<NSThread: 0x600002182200>{number = 1, name = main}
2020-02-01 00:20:02.830285+0800 多线程[5402:867594] 1,<NSThread: 0x600002182200>{number = 1, name = main}
2020-02-01 00:20:03.831774+0800 多线程[5402:867594] 2,<NSThread: 0x600002182200>{number = 1, name = main}
2020-02-01 00:20:04.833280+0800 多线程[5402:867594] 3,<NSThread: 0x600002182200>{number = 1, name = main}
2020-02-01 00:20:05.834919+0800 多线程[5402:867594] 4,<NSThread: 0x600002182200>{number = 1, name = main}
2020-02-01 00:20:05.835200+0800 多线程[5402:867594] 执行完毕
*/

并发队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)test
{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLog(@"开始执行");
dispatch_apply(5, queue, ^(size_t i) {
sleep(1);
NSLog(@"%zu,%@",i,[NSThread currentThread]);
});
NSLog(@"执行完毕");
}
/*
2020-02-01 00:20:21.107718+0800 多线程[5402:867594] 开始执行
2020-02-01 00:20:22.109137+0800 多线程[5402:867736] 1,<NSThread: 0x6000021b0d80>{number = 3, name = (null)}
2020-02-01 00:20:22.109137+0800 多线程[5402:867594] 0,<NSThread: 0x600002182200>{number = 1, name = main}
2020-02-01 00:20:22.109186+0800 多线程[5402:868618] 2,<NSThread: 0x600002111400>{number = 8, name = (null)}
2020-02-01 00:20:22.109190+0800 多线程[5402:868619] 3,<NSThread: 0x600002108ec0>{number = 9, name = (null)}
2020-02-01 00:20:23.109931+0800 多线程[5402:867736] 4,<NSThread: 0x6000021b0d80>{number = 3, name = (null)}
2020-02-01 00:20:23.110251+0800 多线程[5402:867594] 执行完毕
*/
  • 使用场景

在某些场景下使用dispatch_apply会对性能有很大的提升,比如你的代码需要以每个像素为基准来处理计算 image 图片。同时dispatch_apply能够避免一些线程爆炸的情况发生(创建很多线程)

1
2
3
4
5
6
7
8
//危险,可能导致线程爆炸以及死锁
for (int i = 0; i < 999; i++){
dispatch_async(q, ^{...});
}
dispatch_barrier_sync(q, ^{});

//较优选择, GCD 会管理并发
dispatch_apply(999, q, ^(size_t i){...});

在异步中实现同步,并将任务并发执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"开始执行");
dispatch_apply(5, queue, ^(size_t i) {
sleep(1);
NSLog(@"%zu,%@",i,[NSThread currentThread]);
});
NSLog(@"执行完毕");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程");
});
});
/*
2020-02-01 00:12:00.243244+0800 多线程[5320:860216] 开始执行
2020-02-01 00:12:01.246097+0800 多线程[5320:860216] 0,<NSThread: 0x600000d769c0>{number = 5, name = (null)}
2020-02-01 00:12:01.249172+0800 多线程[5320:860352] 3,<NSThread: 0x600000dec600>{number = 8, name = (null)}
2020-02-01 00:12:01.249177+0800 多线程[5320:860351] 2,<NSThread: 0x600000dc2dc0>{number = 7, name = (null)}
2020-02-01 00:12:01.249172+0800 多线程[5320:860353] 1,<NSThread: 0x600000dc2ac0>{number = 9, name = (null)}
2020-02-01 00:12:02.246783+0800 多线程[5320:860216] 4,<NSThread: 0x600000d769c0>{number = 5, name = (null)}
2020-02-01 00:12:02.247289+0800 多线程[5320:860216] 执行完毕
2020-02-01 00:12:02.247622+0800 多线程[5320:860146] 回到主线程
*/

将上面代码的队列改为手动创建的并发队列,任务就不会并发执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"开始执行");
dispatch_apply(5, queue, ^(size_t i) {
sleep(1);
NSLog(@"%zu,%@",i,[NSThread currentThread]);
});
NSLog(@"执行完毕");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"回到主线程");
});
});
/*
2020-02-01 00:15:40.050698+0800 多线程[5364:863969] 开始执行
2020-02-01 00:15:41.056250+0800 多线程[5364:863969] 0,<NSThread: 0x600000824880>{number = 6, name = (null)}
2020-02-01 00:15:42.061385+0800 多线程[5364:863969] 1,<NSThread: 0x600000824880>{number = 6, name = (null)}
2020-02-01 00:15:43.066950+0800 多线程[5364:863969] 2,<NSThread: 0x600000824880>{number = 6, name = (null)}
2020-02-01 00:15:44.069207+0800 多线程[5364:863969] 3,<NSThread: 0x600000824880>{number = 6, name = (null)}
2020-02-01 00:15:45.071144+0800 多线程[5364:863969] 4,<NSThread: 0x600000824880>{number = 6, name = (null)}
2020-02-01 00:15:45.071491+0800 多线程[5364:863969] 执行完毕
2020-02-01 00:15:45.071912+0800 多线程[5364:863900] 回到主线程
*/

Dispatch Source

dispatch 框架提供一套接口用于监听系统底层对象(如文件描述符、Mach 端口、信号量等),当这些对象有事件产生时会自动把事件的处理 block 函数提交到 dispatch 队列中执行,这套接口就是 Dispatch Source APIDispatch Source 其实就是对 kqueue 功能的封装,可以去查看 dispatch_source 的 c 源码实现(什么是 kqueue?Google,什么是 Mach 端口? Google Again),Dispatch Source 主要处理以下几种事件:

1
2
3
4
5
6
7
8
9
10
11
DISPATCH_SOURCE_TYPE_DATA_ADD        变量增加
DISPATCH_SOURCE_TYPE_DATA_OR 变量OR
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 source 自动将一个 block 放入一个 dispatch queue 执行。

GCD定时器

NSTimerCADisplayLink 定时器不准时的问题,解决办法就是使用 GCD 定时器。GCD 的定时器是直接跟系统内核挂钩的,而且它不依赖于RunLoop,所以它非常的准时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
     dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);

//创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置时间(start:几s后开始执行; interval:时间间隔)
uint64_t start = 2.0; //2s后开始执行
uint64_t interval = 1.0; //每隔1s执行
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0);
//设置回调
dispatch_source_set_event_handler(timer, ^{
NSLog(@"%@",[NSThread currentThread]);
});
//启动定时器
dispatch_resume(timer);
NSLog(@"%@",[NSThread currentThread]);

self.timer = timer;
/*
2020-02-01 21:34:23.036474+0800 多线程[7309:1327653] <NSThread: 0x600001a5cfc0>{number = 1, name = main}
2020-02-01 21:34:25.036832+0800 多线程[7309:1327705] <NSThread: 0x600001acb600>{number = 7, name = (null)}
2020-02-01 21:34:26.036977+0800 多线程[7309:1327705] <NSThread: 0x600001acb600>{number = 7, name = (null)}
2020-02-01 21:34:27.036609+0800 多线程[7309:1327707] <NSThread: 0x600001a1e5c0>{number = 4, name = (null)}
*/

dispatch_queue_set_specific & dispatch_get_specific

这两个 API 类似于objc_setAssociatedObjectobjc_getAssociatedObject,FMDB 里就用到这个来防止死锁,来看看 FMDB 的部分源码:

1
2
3
4
5
6
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
//创建一个串行队列来执行数据库的所有操作
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);

//通过key标示队列,设置context为self
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);

当要执行数据库操作时,如果在 queue 里面的 block 执行过程中,又调用了 indatabase 方法,需要检查是不是同一个 queue,因为同一个 queue 的话会产生死锁情况

1
2
3
4
- (void)inDatabase:(void (^)(FMDatabase *db))block {
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
}

dispatch_once_t

dispatch_once_t必须是全局或static变量
这一条算是“老生常谈”了,但我认为还是有必要强调一次,毕竟非全局或非staticdispatch_once_t变量在使用时会导致非常不好排查的bug,正确的如下:

1
2
3
4
5
//静态变量,保证只有一份实例,才能确保只执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//单例代码
});

注意
onceToken实现单例时释放单粒 onceToken的作用域和赋值

dispatch_after

dispatch_after是延迟提交,不是延迟运行
先看看官方文档的说明:

Enqueue a block for execution at the specified time.

Enqueue,就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行!。

看看如下代面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);

//立即打印一条信息
NSLog(@"Begin add block...");

//提交一个block
dispatch_async(queue, ^{
//Sleep 10秒
[NSThread sleepForTimeInterval:10];
NSLog(@"First block done...");
});

//5 秒以后提交block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), queue, ^{
NSLog(@"After...");
});

结果如下:

1
2
3
2015-03-31 20:57:27.122 GCDTest[45633:1812016] Begin add block...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] First block done...
2015-03-31 20:57:37.127 GCDTest[45633:1812041] After...

参考

关于 GCD 的一些总结