GCD 是一套底层级的 C API,通过 GCD 可以方便的进行并发编程。GCD 在后端管理着一个线程池,它决定代码块在哪个线程执行,并根据可用的系统资源对这些线程进行管理。
# 队列 (Dispatch Queue)
队列是一个对象,它会以先进先出的方式管理您提交的任务。
# 串行队列
串行队列将任务以先进先出 (FIFO) 的顺序来执行。多个串行队列之间是并发执行的。如果你创建了 4 个串行队列,每一个队列在同一时间都只执行一个任务,对这四个任务来说,他们是相互独立且并发执行的。
在 Objective-C 中我们可以通过 dispatch_queue_create
并指定队列类型为 DISPATCH_QUEUE_SERIAL
来创建串行队列,若没有指定队列类型则默认为串行队列
// 未指定type则默认为串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
// 指定type为串行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
2
3
4
在 Swift 中我们可以通过 DispatchQueue
来创建串行队列
let queue = DispatchQueue(label: "queue")
# 并行队列
并行队列虽然能同时执行多个任务,但这些任务仍然是按照先到先执行 (FIFO) 的顺序来执行的。并行队列会基于系统负载来合适地选择并发执行这些任务。
在 Objective-C 中我们可以通过指定队列类型为 DISPATCH_QUEUE_CONCURRENT
来创建并行队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
在 Swift 中我们可以通过设置 attributes
参数为 .concurrent
来创建并行队列
let queue = DispatchQueue(label: "queue", attributes: .concurrent)
# 全局队列
全局队列是系统提供的并行队列,与上面我们自己创建的队列不同,全局队列没有名字,无需创建即可使用
# 主队列
主队列是一个串行队列,和其它串行队列一样,这个队列中的任务一次只能执行一个。它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。
在 Objective-C 中我们可以通过 dispatch_get_main_queue
来获得主队列。
dispatch_queue_t queue = dispatch_get_main_queue();
在 Swift 中我们可以通过 main
来获得主队列。
let queue = DispatchQueue.main
# 队列优先级
优先级决定了对一个任务分配资源的大小,并非绝对的执行顺序。
下面是 8.0 之后的服务优先级参数,从上到下优先级依次降低,系统会通过合理的资源控制来最高效的执行任务代码
优先级 | Objective-C 参数 | Swift 参数 | 值 |
---|---|---|---|
用户交互 | QOS_CLASS_USER_INTERACTIVE | userInteractive | 0x21 |
用户期望 | QOS_CLASS_USER_INITIATED | userInitiated | 0x19 |
默认 | QOS_CLASS_DEFAULT | default | 0x15 |
实用工具 | QOS_CLASS_UTILITY | utility | 0x11 |
后台 | QOS_CLASS_BACKGROUND | background | 0x09 |
未指定 | QOS_CLASS_UNSPECIFIED | unspecified | 0x00 |
下面是 7.0 之前的优先级参数
优先级 | 参数 | 值 |
---|---|---|
高 | DISPATCH_QUEUE_PRIORITY_HIGH | 2 |
默认 | DISPATCH_QUEUE_PRIORITY_DEFAULT | 0 |
低 | DISPATCH_QUEUE_PRIORITY_LOW | -2 |
后台 | DISPATCH_QUEUE_PRIORITY_BACKGROUND | INT16_MIN |
在 Objective-C 中我们可以改变全局队列的优先级
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
在 Swift 中我们可以在创建队列时或在全局队列中设置 qos
参数来设置优先级
// 创建队列时设置优先级
let queue = DispatchQueue(label: "queue", qos: .userInteractive)
// 变更全局队列优先级
let queue = DispatchQueue.global(qos: .default)
2
3
4
5
# 任务
GCD 中的任务只是一个代码块,它可以指一个 block 或者函数指针。根据这个代码块添加进入队列的方式,将任务分为同步任务和异步任务:
# 同步任务
加入队列的同步任务会被顺序执行
在 Objective-C 中我们通过 dispatch_sync
来将同步任务添加到队列中
// 队列中添加同步任务
dispatch_sync(dispatch_queue_create("queue", NULL), ^{
// 任务
...
});
2
3
4
5
在 Swift 中我们通过在队列后调用 sync
来将同步任务添加到队列中
// 队列中添加同步任务
DispatchQueue(label: "queue").sync {
// 任务
...
}
2
3
4
5
将同步任务加入串行队列,队列中的任务会顺序执行,注意在一个任务未结束时调起其它同步任务会死锁。
将同步任务加入并行队列,队列中的任务会顺序执行,没有什么用。
注意
如果在一个串行队列 (如主队列) Q 上的同步任务 A 中再次添加一个同步任务 B,并且指定 B 的队列也为串行队列 Q,此时任务 B 被放到队列的队尾等待执行,因为这是一个串行队列,所以任务 A 会等待任务 B 先执行,而这个任务永远没有机会执行,此时队列被永远阻塞造成死锁。 (这就是在主线程调用 dispatch_sync
函数,并且在 dispatch_sync
函数中传入 main_queue
作为队列造成死锁的情况)
// 在主线程调用
dispatch_sync(dispatch_get_main_queue(), ^{
// 死锁
});
2
3
4
# 异步任务
在 Objective-C 中我们通过 dispatch_async
来将异步任务添加到队列中
// 队列中添加异步任务
dispatch_async(dispatch_queue_create("queue", NULL), ^{
// 任务
...
});
2
3
4
5
在 Swift 中我们通过在队列后调用 async
来将异步任务添加到队列中
// 队列中添加异步任务
DispatchQueue(label: "queue").async {
// 任务
...
}
2
3
4
5
将异步任务加入串行队列,整个队列并行执行,队列中的任务会顺序执行,并且不会出现死锁问题。
将异步任务加入并行队列,会并行执行多个任务,这也是我们最常用的一种方式。
# 组 (Dispatch Group)
我们可以将多个队列添加到一个组中,在这多个任务完成后我们可以获得通知
# 创建组
在 Objective-C 中我们通过 dispatch_group_create
来创建组
dispatch_group_t group = dispatch_group_create();
在 Swift 中我们通过 DispatchGroup
来创建组
let group = DispatchGroup()
# 将队列添加到组中
// 创建组
dispatch_group_t group = dispatch_group_create();
// 将队列任务添加到组
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 任务
});
2
3
4
5
6
// 创建组
let group = DispatchGroup()
// 将队列任务添加到组
DispatchQueue.global().async(group: group) {
// 任务
}
2
3
4
5
6
# 通过 enter
、 leave
将队列添加到组中
enter
和 leave
这两个函数必须成对出现,否则这一组任务就永远执行不完。
// 创建组
dispatch_group_t group = dispatch_group_create();
// 进入组
dispatch_group_enter(group);
// 创建任务
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 任务
...
// 退出组
dispatch_group_leave(group);
});
2
3
4
5
6
7
8
9
10
11
// 创建组
let group = DispatchGroup()
// 进入组
group.enter()
// 创建任务
DispatchQueue.global().async {
// 任务
...
// 退出组
group.leave()
}
2
3
4
5
6
7
8
9
10
11
# 组内任务完成后通知
在 Objective-C 中 dispatch_group_notify
可以在组内任务全部完成后执行方法
// 创建组
dispatch_group_t group = dispatch_group_create();
// 将队列任务添加到组
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 任务1
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 任务2
});
// 等待group中多个任务执行完毕,做一些事情
dispatch_group_notify(group, mainQueue, ^{
// 任务完成后,在主队列中做一些操作
...
});
2
3
4
5
6
7
8
9
10
11
12
13
14
在 Swift 中 notify
可以在组内任务全部完成后执行方法
// 创建组
let group = DispatchGroup()
// 将队列任务添加到组
DispatchQueue.global().async(group: group) {
// 任务1
}
DispatchQueue.global().async(group: group) {
// 任务2
}
// 等待 group 中多个任务执行完毕,做一些事情
group.notify(queue: .main) {
// 任务完成后,在主队列中做一些操作
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# GCD 常见用法和应用场景
# 延迟执行
dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
// 在queue里面延迟 5s 执行任务
...
}
2
3
4
5
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
// 在queue里面延迟 5s 执行任务
...
}
2
3
4
# 方法只执行一次
Swift 不支持该方法,需要通过其他方式实现前往实现方式
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行一次的任务
...
});
2
3
4
5
# Dispatch_barrier
Dispatch_barrier
的作用可以用一个词概括--承上启下,它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。本例中,任务 2 会在任务 1 执行完之后执行,而任务 3 会等待任务 2 执行完后执行。
// 创建队列
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 任务1
...
});
dispatch_barrier_async(queue, ^{
// 任务2
...
});
dispatch_async(queue, ^{
// 任务3
...
});
2
3
4
5
6
7
8
9
10
11
12
13
14
Swift 需要通过 flags
参数来实现
// 创建队列
let queue = DispatchQueue(label: "queue", attributes: .concurrent)
queue.async {
// 任务1
...
}
queue.async(flags: .barrier) {
// 任务2
...
}
queue.async {
// 任务3
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
注意
Dispatch_barrier
只在自己创建的并发队列上有效,在全局并发队列、串行队列上无效
# Dispatch_apply
Dispatch_apply
可以按指定的次数将指定的任务追加到指定的队列中,因为 Dispatch_apply
并行的运行机制,效率一般快于 for 循环(在 for 一次循环中的处理任务很多时差距比较大)。比如这可以用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性。当需要循环处理一些事情且处理顺序对结果无关时可以考虑使用该方式
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
// 循环 10 次
NSLog(@"%zu", index);
});
2
3
4
Swift 中使用 concurrentPerform
DispatchQueue.concurrentPerform(iterations: 10) { index in
// 循环 10 次
print("\(index)")
}
2
3
4
# 队列挂起与恢复
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_suspend(queue); //暂停队列queue
dispatch_resume(queue); //恢复队列queue
2
3
let queue = DispatchQueue(label: "queue", attributes: .concurrent)
queue.resume()
queue.suspend()
2
3
# 定时器
GCD 可以通过 Dispatch Source 来实现定时器,与 Timer 相比 GCD 定时器更加精准,并且不依赖于 Runloop
// 在主线程创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 设置计时的时间
// DISPATCH_TIME_NOW 表示从现在开始计时
// 第三个参数代表间隔时间
// 第四个参数是精准度(0代表绝对精准)
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
// 设置定时任务
dispatch_source_set_event_handler(timer, ^{
// 定时执行任务
});
// 开始执行
dispatch_resume(timer);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 在主线程创建定时器
let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.main)
// 设置计时的时间
timer.schedule(deadline: .now(), repeating: .seconds(1), leeway: .seconds(0))
// 设置定时任务
timer.setEventHandler {
// 定时执行任务
}
// 开始执行
timer.resume()
2
3
4
5
6
7
8
9
10
定时器的控制方法
dispatch_activate()
可以在 Timer 被创建后激活 Timer。该方法在 iOS 10 版本后可用,之前的版本可以用dispatch_resume()
来激活dispatch_suspend()
可以让运行中的 Timer 挂起即暂停dispatch_resume()
可以用来将挂起中的 Timer 唤醒来继续运行,也可以在 Timer 被创建后激活 Timerdispatch_source_cancel()
可以取消 Timer,被取消的 Timer 会被销毁
注意事项
dispatch_suspend()
与dispatch_resume()
必须成对出现,除了创建完毕第一次激活 Timer 可以先调用dispatch_resume()
外,如果没有挂起就调用会导致应用崩溃- 当 Timer 处于挂起状态时,不能够将 Timer 置为 nil 或者调用
dispatch_source_cancel()
方法,必须先激活才能取消,否则应用会崩溃
DispatchTime 与 DispatchWallTime
在设置时间时有 DispatchTime 与 DispatchWallTime 两种选择,他们的主要区别是 DispatchTime 在应用进入休眠如退出到后台时会暂停而 DispatchWallTime 则会继续运行
# 信号量 Semaphore
# 创建信号量
在 Objective-C 中通过 dispatch_semaphore_create
创建信号量
// 创建信号量 设置信号量计数值为 3
dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);
2
在 Swift 中通过 DispatchSemaphore
创建信号量
// 创建信号量 设置信号量计数值为 3
let semaphore = DispatchSemaphore(value: 3)
2
# 使用信号量
// 若 semaphore 计数为0则等待,大于0则使其减1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 使 semaphore 计数加1
dispatch_semaphore_signal(semaphore);
2
3
4
在 Swift 中通过 DispatchSemaphore
创建信号量
// 若 semaphore 计数为0则等待,大于0则使其减1
semaphore.wait()
// 使 semaphore 计数加1
semaphore.signal()
2
3
4
# 解决同步问题
下面的输出肯定为 1、2、3。
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
NSLog(@"1\n");
dispatch_semaphore_signal(semaphore2);
dispatch_semaphore_signal(semaphore1);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
NSLog(@"2\n");
dispatch_semaphore_signal(semaphore3);
dispatch_semaphore_signal(semaphore2);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
NSLog(@"3\n");
dispatch_semaphore_signal(semaphore3);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let queue = DispatchQueue.global()
let semaphore1 = DispatchSemaphore(value: 1)
let semaphore2 = DispatchSemaphore(value: 0)
let semaphore3 = DispatchSemaphore(value: 0)
queue.async {
semaphore1.wait()
print("1")
semaphore2.signal()
semaphore1.signal()
}
queue.async {
semaphore2.wait()
print("2")
semaphore3.signal()
semaphore2.signal()
}
queue.async {
semaphore3.wait()
print("3")
semaphore3.signal()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 解决有限资源访问问题
for 循环看似能创建 100 个异步任务,实质由于信号限制,最多创建 10 个异步任务。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 任务
...
dispatch_semaphore_signal(semaphore);
});
}
2
3
4
5
6
7
8
9
在 Swift 中通过 DispatchSemaphore
创建信号量
let semaphore = DispatchSemaphore(value: 10)
for var i in 0 ..< 100 {
semaphore.wait()
DispatchQueue.global().async {
semaphore.signal()
}
}
2
3
4
5
6
7
# Dispatch I/O
# 异步串行读取文件
NSString *desktop = @"/Users/XXX/Desktop";
NSString *path = [desktop stringByAppendingPathComponent:@"main.m"];
// 当设置为并行队列时在读取文件时实际还是串行
dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
dispatch_fd_t fd = open(path.UTF8String, O_RDONLY, 0);
dispatch_io_t io = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue, ^(int error) {
close(fd);
});
size_t water = 1024*1024;
dispatch_io_set_low_water(io, water);
dispatch_io_set_high_water(io, water);
long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
NSMutableData *totalData = [[NSMutableData alloc] init];
dispatch_io_read(io, 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 *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
}
});
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
# 异步并行读取文件
NSString *desktop = @"/Users/XXX/Desktop";
NSString *path = [desktop stringByAppendingPathComponent:@"main.m"];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_fd_t fd = open(path.UTF8String, O_RDONLY);
dispatch_io_t io = dispatch_io_create(DISPATCH_IO_RANDOM, fd, queue, ^(int error) {
close(fd);
});
off_t currentSize = 0;
long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
size_t offset = 1024*1024;
dispatch_group_t group = dispatch_group_create();
NSMutableData *totalData = [[NSMutableData alloc] initWithLength:fileSize];
for (; currentSize <= fileSize; currentSize += offset) {
dispatch_group_enter(group);
dispatch_io_read(io, currentSize, offset, queue, ^(bool done, dispatch_data_t _Nullable data, int error) {
if (error == 0) {
size_t len = dispatch_data_get_size(data);
if (len > 0) {
const void *bytes = NULL;
(void)dispatch_data_create_map(data, (const void **)&bytes, &len);
[totalData replaceBytesInRange:NSMakeRange(currentSize, len) withBytes:bytes length:len];
}
}
if (done) {
dispatch_group_leave(group);
}
});
}
dispatch_group_notify(group, queue, ^{
NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
NSLog(@"%@", str);
});
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
dispatch_io_create_with_path
通过路径创建一个 dispatch_io_t
。与之相似的也有好几个方法,比如 dispatch_io_create_with_io
dispatch_io_set_high_water
和 dispatch_io_set_low_water
分割文件大小,分别可以设置一次最少读取和一次最多读取多大。
dispatch_io_read
读取文件。与之对应的是 dispatch_io_write
将文件存储到指定路径。如果想提高文件读取速度,可以尝试使用 Dispatch I/O。