14

关于多线程编程与GCD相关知识点整理

 3 years ago
source link: https://xnxy.github.io/xnxy.github.io/2020/12/02/%E5%85%B3%E4%BA%8E%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%BC%96%E7%A8%8B%E4%B8%8EGCD%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86%E7%82%B9%E6%95%B4%E7%90%86/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Overview

最近重新阅读了《Objective-C高级编程iOS与OS X 多线程和内存管理》,于是决定将GCD相关知识整理下,便于后期查阅。

本文主要内容来源于《Objective-C高级编程iOS与OS X 多线程和内存管理》和网络。

GCD

Grand Central Dispatch (GCD)概要

什么是GCD

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样比以前的线程更有效率。

下面为简单使用GCD的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
/*
异步执行长时间操作
例如:数据库访问等

长时间处理结束,主线程使用处理结果
*/
dispatch_async(dispatch_get_main_queue(), ^{
/*
只在主线程可以执行的处理
例如:页面刷新
*/
});
});

在使用GCD之前,Cocoa框架提供了NSObject类的performSelectorInBackground:withObject:performSelectorOnMainThread:withObject:waitUntilDone:实例方法等简单的多线程编程技术。

例如上方的示例可以写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)performFunction{
[self performSelectorInBackground:@selector(doWork) withObject:nil];
}

- (void)doWork{
@autoreleasepool {
/*
异步执行长时间操作
例如:数据库访问等

长时间处理结束,主线程使用处理结果
*/
[self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
}
}

- (void)doneWork{
/*
只在主线程可以执行的处理
例如:页面刷新
*/
}

多线程编程

由于一个CPU一次只能执行一个命令,不能执行某处分来的并列的两个命令,因此通过CPU执行的CPU命令就好比一条无分叉的大道,其执行不会出现分歧。

这里所说的“1个CPU执行的CPU命令列为一条无分叉路径”即为“线程”。

现在一个物理的CPU芯片实际上有64个(64核)CPU,如果1个CPU核虚拟为两个CPU核工作,那么一台计算机上使用多个CPU核就是理所当然的事了。尽管如此,“1个CPU核执行的CPU命令列为一条无分叉路径”仍然不变。

这种无分叉路径不只1条,存在有多条时即为“多线程”。在多线程中,1个CPU核执行多条不同路径上的不同命令。

OS XiOS的核心XNU内核再发生操作系统事件时(如每隔一定事件,换起系统调用等情况)会切换执行路径。执行中路径的状态,例如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令序列。这杯称为上下文切换

由于使用多线程的程序可以再某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好笑1个CPU核能够并列执行多个线程一样。而且在具有多个CPU核的情况下,就不是“看上去像”了,而是真的提供了多个CPU核并行执行多个线程技术。

这种利用多线程编程的技术就被称为“`多线程编程

但是,多线程编程实际上是一种易发生各种问题的编程技术。比如多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程相互持续等待(死锁)、使用太多线程会消耗大量内存等。

尽管极易发生各种问题,也应当使用多线程编程。因为使用多线程编程可保证应用程序的响应性能。

GCD的API

Dispatch 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种类 说明 Serial Dispatch Queue(串行调度队列) 等待现在执行中处理结束 Concurrent Dispatch Queue(并发调度队列) 不等待现在执行中处理结束

dispatch_queue_create

通过didpatch_queue_create函数可生成Dispatch Queue

1
2
3
4
 // 串行队列的创建方法
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.example.gcd.serial", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.example.gcd.concurrent", DISPATCH_QUEUE_CONCURRENT);

Main Dispatch Queue/Global Dispatch Queue

第二种方法是获取系统标准提供的Dispatch Queue

实际上不用特意生成Dispatch Queue系统也会给我们提供几个。那就是Main Dispatch QueueGlobal Dispatch Queue

Main Dispatch Queue正如其名称中含有的Main一样,是在主线程中执行的Dispatch Queue。因为主线程只有1个,所以Main Dispatch Queue自然就是Serial Dispatch Queue

追加到Main Dispatch Queue的处理在主线程的RunLoop中执行。由于再主线程中执行,因此要哦将用户界面的界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue使用。

1
2
//主线程队列获取
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

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的线程,将各自使用的Global Dispatch Queue的执行优先级作为线程的执行优先级使用。在向Global Dispatch Queue追加处理时,应选择与处理内容对应的执行优先级的Global Dispatch Queue

但通过XNU内核用于Global Dispatch Queue的线程并不能保证实时性,因此执行优先级只是大致的判断。例如再处理内容的执行可有可无时,使用后台优先级的Global Dispatch Queue等,只能进行这种程度的区分。

名称 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 执行优先级:后台

各种 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
/*
* Main Dispatch Queue 的获取方法
*/
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

/*
* Global Dispatch Queue (高优先级)的获取方法
*/
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

/*
* Global Dispatch Queue (默认优先级)的获取方法
*/
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

/*
* Global Dispatch Queue (低优先级)的获取方法
*/
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

/*
* Global Dispatch Queue (后台优先级)的获取方法
*/
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

dispatch_set_target_queue

dispatch_queue_create函数生成的Dispatch Queue不管是Serial Dispatch Queue(串行队列)还是Concurrent Dispatch Queue(并行队列)都使用默认优先级Global Dispatch Queue相同执行优先级的线程。而变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数。在后台执行动作处理的Serial Dispatch Queue的生成方法如下:

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

指定要变更执行优先级的Dispatch Queuedispatch_set_target_queue函数的第一个参数,指定与要使用的优先级相同优先级的Global Dispatch Queue为第二个参数(目标)。 第一个参数如果指定系统提供的Main Dispatch QueueGlobal Dispatch Queue则不知道会出现什么状况,因此这些均不可指定。

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中时,如果使用dispatch_set_target_queue函数将目标指定为某一个Serial Dispatch Queue,即可防止处理并执行。

dispatch_after

如果想在指定时间后执行某操作,可以使用dispatch_after函数来实现。

1
2
3
4
5
6
7
8
9
10
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"--- 延迟3秒后执行的操作 ---");
});

//或

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.f * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"--- 延迟3秒后执行的操作 ---");
});

需要注意的是,dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间追加处理到Dispatch Queue。此源代码与再3秒后用dispatch_async函数追加BlockMain Dispatch Queue相同。

Dispatch_Group

在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这种情况会经常出现。只使用一个Serial Dispatch Queue(串行调度队列)时,只要将想执行的处理全部追加到该Serial Dispatch Queue中并在最后追加结束处理,即可实现。但是在使用Concurrent Dispatch Queue(并发调度队列)时或同时使用多个Dispatch Queue时,源代码就会变得颇为复杂。

在此种情况下使用Dispatch Group。例如下面的源代码为:追加3个Block到Global Dispatch Queue(全局调度队列),这些Block如果全部执行完毕,就会执行Main Dispatch Queue中结束处理用的Block.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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(@"---blk1---");
});

dispatch_group_async(group, queue, ^{
NSLog(@"---blk2---");
});

dispatch_group_async(group, queue, ^{
NSLog(@"---blk3---");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"---done---");
});

因为向Global Dispatch Queue(全局调度队列)Concurrent Dispatch Queue(并发调度队列)追加处理,多个线程并行执行,所以追加处理的执行顺序不定。执行时会发生变化,但是此执行结果的done一定是最后输出的。

另外,在Dispatch Group中也可以使用dispatch_group_wait函数仅等待全部处理结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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(@"---blk1---");
});

dispatch_group_async(group, queue, ^{
NSLog(@"---blk2---");
});

dispatch_group_async(group, queue, ^{
NSLog(@"---blk3---");
});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_wait函数的第二个参数指定为等待的时间(超时)。它属于dispatch_time_t类型的值。该源代码使用DISPATCH_TIME_FOREVER,意味着永远等待。

如果需要指定等待事件,则需要如下处理:

1
2
3
4
5
6
7
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)1 * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
//属于 Dispatch Group 的全部处理执行结束
}else{
//属于 Dispatch Group 的某一个处理还在执行中
}

指定DISPATCH_TIME_NOW,则代表不用任何等待即判断Dispatch Group是否处理完。

1
dispatch_group_wait(group, DISPATCH_TIME_NOW);

dispatch_barrier_async

在访问数据库或文件时,使用Serial Dispatch Queue(串行队列)可避免数据竞争的问题。

写入处理确实不可与其他的写入处理以及包含数据处理的其他某些处理并行执行。但是如果读取处理只与读取处理并行执行,那么多个并行执行就不会发生问题。

也就是说,为了高效率地进行访问,读取处理追加到Concurrent Dispatch Queue(并发调度队列)中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue(串行队列)中即可(在写入处理结束之前,读取处理不可执行)。

虽然利用Dispatch Groupdispatch_set_target_queue函数也可实现,但是源代码会很复杂。

GCD为我们提供了更为聪明的解决方法——dispatch_barrier_async函数。该函数同dispatch_queue_create函数生成的Concurrent Dispatch Queue一起使用。

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
24
25
26
27
28
29
30
31
32
33
34
35
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.barrier", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async1");
});

dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async2");
});

//等待前面的任务执行完毕后自己才执行,后面的任务需等待它完成之后才执行
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:4];
NSLog(@"四秒后:dispatch_barrier_async");
});

dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});

dispatch_async(queue, ^{
NSLog(@"dispatch_async4");
});

打印结果:
dispatch_async2
dispatch_async1
dispatch_barrier_async
四秒后:dispatch_barrier_async
dispatch_async4
dispatch_async3

dispatch_sync

dispatch_async函数的async意味着“非同步”(asynchronous),就是将指定的Block“非同步”地追加到指定的Dispatch Queue中。dispatch_async函数不做任何等待。

既然有“async”,当然也就有“sync”,即dispatch_sync函数。它意味着“同步”(synchronous),也就是将指定的Block“同步”追加到指定的Dispatch Queue中。在追加Block结束之前,dispatch_sync函数会一直等待。

dispatch_apply

dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的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);
});
NSLog(@"---done---");
1
2
3
4
5
6
7
8
9
10
11
12
日志打印结果:
---0---
---1---
---2---
---3---
---4---
---5---
---6---
---7---
---8---
---9---
---done---

dispatch_suspend/dispatch_resume

当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理。例如演算结果被Block截获时,一些处理会对这个演算结果造成影响。

在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再恢复。

dispatch_suspend函数挂起指定的Dispatch Queue.

dispatch_resume函数恢复指定的Dispatch Queue

这些函数对已经执行的处理没有影响。挂起后,追加到Dispatch Queue中但尚未执行的处理再此之后停止执行。而恢复则使得这些处理能够继续执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.suspend", DISPATCH_QUEUE_CONCURRENT);
dispatch_suspend(queue);
dispatch_async(queue, ^{
dispatch_apply(5, queue, ^(size_t index) {
NSLog(@"---%ld---1----",index);
});
});
sleep(1);
NSLog(@"---2---");
dispatch_resume(queue);

日志打印:
---2---
---0---1----
---1---1----
---2---1----
---3---1----
---4---1----

Dispatch Semaphore

当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束。虽然用Serial Dispatch Queuedispatch_barrier_async函数可避免这类问题,但有必要进行更细粒度的排他控制。在此就可以使用信号量。

Dispatch Semaphore(信号量)是持有计数的信号,该计数是多线程编程中的计数类型信号。所谓信号,类似于过马路时常用的手旗。可以通过时举起手旗,不可通过时放下手旗。而在Dispatch Semaphore中,使用计数来实现该功能。计数为0时等待,计数为1或大于1时,减去1而不等待。

1
2
3
dispatch_semaphore_t semaphore = dispatch_semaphore_create(intptr_t value); // 创建信号量
dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema); // 发送信号量
dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout) // 等待信号量

用法示例:

1
2
3
4
5
6
7
8
9
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

dispatch_async(dispatch_queue_create("com.example.gcd.queue", DISPATCH_QUEUE_CONCURRENT), ^{
sleep(1);
NSLog(@"---1---");
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"---done---");

dispatch_semaphore_wait函数的返回值也与dispatch_group_wait函数相同。可像以下源代码这样,通过返回值进行分支处理。

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
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

dispatch_async(dispatch_queue_create("com.example.gcd.queue", DISPATCH_QUEUE_CONCURRENT), ^{
sleep(2);
NSLog(@"---1---");
dispatch_semaphore_signal(semaphore);
});

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t) 1 * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0) {
/*
* 由于Dispatch Semaphore 的计数值达到大于等于1
* 或者在待机中的指定时间内
* Dispatch Semaphore 的计数值达到大于等于1
*
* 可执行需要进行排他控制的处理
*/
NSLog(@"---2---");
}else{
/*
* 由于 Dispatch Semaphore 的计数值为0
* 因此再达到指定时间为止待机
*/
NSLog(@"---3---");
}

日志打印:

---3---
---1---

在没有Serial Dispatch Queuedispatch_barrier_async函数那么大粒度且一部分处理需要进行排他控制的情况下,Dispatch Semaphore便可发挥威力。

dispatch_once

dispatch_once函数是保证再应用程序中共只执行一次指定处理的API。

1
2
3
4
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//初始化
});

Dispatch I/O

在读取较大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue并列读取的话,应该会比一般的读取快很多。现今的输入/输出硬件已经可以做到一次使用多个线程更快地并列读取了。能实现这一功能的就是Dispatch I/ODispatch Data

通过Dispatch I/O读取文件时,使用Global Dispatch Queue将1个文件按某个大小read/write

1
2
3
4
5
6
7
8
dispatch_async(queue, ^{ /* 读取  0     ~ 8080  字节*/ });
dispatch_async(queue, ^{ /* 读取 8081 ~ 16383 字节*/ });
dispatch_async(queue, ^{ /* 读取 16384 ~ 24575 字节*/ });
dispatch_async(queue, ^{ /* 读取 24576 ~ 32767 字节*/ });
dispatch_async(queue, ^{ /* 读取 32768 ~ 40959 字节*/ });
dispatch_async(queue, ^{ /* 读取 40960 ~ 49191 字节*/ });
dispatch_async(queue, ^{ /* 读取 49192 ~ 57343 字节*/ });
dispatch_async(queue, ^{ /* 读取 57344 ~ 65535 字节*/ });

可以像上方这样,将文件分割为一块一块地进行读取处理。分割读取的数据通过使用Dispatch Data可更为简单低进行结合和分割。

异步串行读取文件

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
NSString *path = [[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingPathComponent:@"/test/Linux Shell脚本攻略.pdf"];
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.serial", DISPATCH_QUEUE_SERIAL);

/** 文件描述符 */
dispatch_fd_t fd = open(path.UTF8String, O_RDONLY, 0);
/** 创建一个调度I/O通道,并将其与指定的文件描述符关联 */
dispatch_io_t io_t = dispatch_io_create(DISPATCH_IO_RANDOM, fd, queue, ^(int error) {
close(fd);
});
size_t water = 1024*1024;
/** 设置一次读取的最小字节大小 */
dispatch_io_set_low_water(io_t, water);
/** 设置一次读取的最大字节 */
dispatch_io_set_high_water(io_t, water);
long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
NSMutableData *totalData = [[NSMutableData alloc] init];
/** 进行文件读取 */
dispatch_io_read(io_t, 0, fileSize, queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
if (error == 0) {
size_t len = dispatch_data_get_size(data);
if (len > 0) {
[totalData appendData:(NSData *)data];
}
}
if (done) {
//将读取的文件存到沙盒中
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/shell.pdf"];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:totalData attributes:nil];
}
});

GCD实现

Dispatch Queue

GCD的Dispatch Queue非常方便,那么它究竟是如何实现的呢?

  • 用于管理追加的Block的C语言层实现的FIFO队列
  • Atomic函数中实现的用于排他控制的轻量级信号
  • 用于管理线程的C语言层实现的一些容器

通常,应用程序中编写的线程管理用的代码要再系统级实现。

实际上正如这句话所说,在系统级即iOSOS X的核心XNU内核级上实现。

因此,无论编程人员入户努力编写管理线程的代码,在性能方面也不可能胜过XNU内核级所实现的GCD。

所以使用GCD要比使用pthreadsNSThread这些一般的多线程编程API更好。并且,如果使用GCD就不必编写为操作线程反复出现的类似的源代码(这被称为固定源代码片段),而可以在线程中集中实现处理内容。所以我们尽量使用GCD或者使用了Cocoa框架GCD的NSOperationQueue类等API。


用于实现Dispatch Queue而使用的软件组件

组件名称 提供技术 libdispatch Dispatch Queue Libc(pthreads) pthread_workqueue XNU内核 workqueue

编程人员所使用GCD的API全部为包含在libdispatch库中的C语言函数。 Dispatch Queue通过结构体和链表,被实现为FIFO队列。FIFO队列管理是通过dispatch_async等函数所追加的Block。

Block 并不是直接加入FIFO队列,而是先加入Dispatch Continuation这个一dispatch_continuation_t类型结构体中,然后再加入FIFO队列。该Dispatch Continuation用于记忆Block所属的Dispatch Group和其他一些信息,相当于一般常说的执行上下文。

Dispatch Queue可通过dispatch_set_target_queue函数设定,可以设定执行该Dispatch Queue处理的Dispatch Queue为目标。该目标可像串珠子一样,设定多个连接再一起的Dispatch Queue。但是在连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于serial Dispatch Queue的各种优先级的Global Dispatch Queue

Dispatch Source

GCD中除了主要的Dispatch Queue外,还有不太引人注目的Dispatch Source。它是BSD系内核惯有功能kqueue的包装。

kqueue是在XNU内核中共发生各种事件时,在应用程序编程方执行处理的技术。其CPU负荷非常小,尽量不占用资源。kqueue可以说是应用程序处理XNU内核中发生的各种事件的方法中最优秀的一种。

Dispatch Source可处理以下事件。


Dispatch Source的种类

名称 内容 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_PROC 检测到与进程相关的事件 DISPATCH_SOURCE_TYPE_READ 可读取文件映像 DISPATCH_SOURCE_TYPE_SIGNAL 接收信号 DISPATCH_SOURCE_TYPE_TIMER 定时器 DISPATCH_SOURCE_TYPE_VNODE 文件系统有变更 DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK