本篇只介绍 GCD 的一些基础知识,包括创建队列的方式和一些参数介绍,以及相关的应用场景。关于更多 api 的调用方式,后面会开一篇 GCD 实践篇专门介绍~
GCD 全称是Grand Central Dispatch
是 Apple 开发的一个多核编程问题的解决方案,与 NSOperation、NSThread、pthread 是 iOS 中多线程的几种处理方式。
C++ 多线程基本应用
之前在 C++ 中怎么使用多线程呢?最简单的就是这么用:
- 提供一个任务 c 函数
- 创建一个新线程
pthread_create
,并向这个线程提交一个任务函数
- 线程结束的时候调用
pthread_exit
来回收资源(main函数要调用pthread_exit
等待其他线程执行完毕)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//1.提供一个任务c函数
void *GCDTest(void *threadid)
{
printf("Hello World!\n");
//3.线程结束调用pthread_exit
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{ //2.创建一个新线程线程
pthread_t thread;
int rc = pthread_create(&thread, NULL, PrintHello, (void *) t);
//4.main函数要调用pthread_exit等待其他线程执行完毕)
pthread_exit(NULL);
}
|
大多数场景下,多线程都使用在异步并发场景。但也有少数场景有些特殊要求:例如要求阻塞,当之前创建的线程执行完退出之前,我们什么都不能做
- 在上述的
pthread_create
的基础上,将其设置成可被 join 的属性,join 方法会阻塞调用它的线程,直到被 join 的线程完成执行。这确保了线程的同步。
- 使用
pthread_join
函数同步等待线程执行完毕
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
int main(int argc, char *argv[])
{
pthread_t thread;
//1.将其设置成可被join的属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
int rc = pthread_create(&thread, &attr, PrintHello, (void *) t);
//记得手动释放
pthread_attr_destroy(&attr);
//2.使用pthread_join函数同步等待线程执行完毕
void *status;
rc = pthread_join(thread, &status);
...
...
pthread_exit(NULL);
}
|
还有些情况下,我们还希望创建的几个多线程任务是能够按照先后顺序执行的。可以尝试信号量、或者接连好几个 join 等方法 … GCD 封装之后的多线程使用起来就会变得比较容易一些:
- 创建一个队列
- 向队列提交一个任务
GCD 让开发者仅关注自己的任务想要被如何执行(串行并发?同步异步?),从而隐藏了创建线程的过程,产生了队列的概念。我们仅需要用的 GCD 函数向 GCD 队列中提交我们的任务即可。
GCD 多线程基本概念
大部分基础概念建议可以参考这篇博客多线程原理,我这里只补充一部分。
- 队列:
- 队列是负责任务调度的。我们向队列中提交了任务之后,queue 会调度此任务并分配线程来执行。
- 分类:串行队列:只能按照 FIFO 原则进行调度,并发队列则没有此限制
- 同步函数:
- 会保证得到任务的执行结果之后再返回,否则就会阻塞下面代码执行
- 实际上不会开辟新的线程,就是在当前线程执行任务 block
- 异步函数:
- 不会等待任务执行结果,提交任务后立即返回
- queue 会开辟新线程来执行
- 任务的执行顺序:
- 不是被提交的任务就会立即执行,会等待被调度。
- 不是被调度了就会立即执行,而是要看其被分配的线程的运行情况。
- 不是先被执行了就会先得到结果,要看任务本身耗时
基础队列的创建和使用
Objective-C 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
- (void)GCDTest {
// 创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.myserial", DISPATCH_QUEUE_SERIAL); // 第二个参数也可以填 NULL
// 创建并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.myconcurrent", DISPATCH_QUEUE_CONCURRENT);
// 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 获取 default 级别的全局队列
dispatch_queue_t globalBackground = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0); // 第一个参数也可以填旧版的 DISPATCH_QUEUE_PRIORITY_DEFAULT
// 同步提交任务
dispatch_sync(serialQueue, ^{
NSLog(@"提交同步任务");
});
// 异步提交任务
dispatch_async(concurrent, ^{
NSlog(@"提交异步任务");
});
}
|
Swift 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
fun GCDTest {
// 创建串行队列
let serialQueue = DispatchQueue(label: "com.example.myserial")
// 创建并发队列
let concurrentQueue = DispatchQueue(label: "com.example.myconcurrent", attributes: .comcurrent)
// 获取主队列
let mainQueue = DispatchQueue.main
// 获取 default 级别的全局队列
let globalDefault = DispatchQueue.global()
serialQueue.sync {
print("提交同步任务")
}
concurrentQueue.async {
print("提交异步任务")
}
}
|
GCD 中的全局并发队列实际上是一组不同 QoS 优先级的并发队列,按优先级由高到低依次执行。
QoS
在计算机科学中,QoS(Quality of Service)是指服务质量的一种度量,用于描述网络和计算资源在一定
时间内能够提供的性能水平。在操作系统和并行编程中,QoS 也用于指定任务的优先级和资源调度策略。不同的 QoS 级别可以影响任务的调度、执行优先级和资源分配。
QoS 的应用领域
- 网络服务:在网络通信中,QoS 用于确保不同类型的数据流(如视频、音频、文件传输等)在网络上传输时具有不同的优先级和带宽保障,从而提供不同的服务质量。
- 操作系统和并行编程:在操作系统和并行编程中,QoS 用于指定任务的优先级,影响任务在多核处理器上的调度和执行。
在 iOS 和 macOS 中,QoS 被用于调度线程和任务,以便系统可以根据任务的重要性和紧急程度来分配 CPU 资源。通过合理使用 QoS,可以优化系统资源的使用,提升用户体验,并有效管理电源。Apple 的 GCD(Grand Central Dispatch)和 NSOperationQueue 框架都支持 QoS。
QoS 等级
在 iOS 和 macOS 中,QoS 级别分为以下几个等级,从最高优先级到最低优先级依次是:
- User Interactive:
用于需要立即执行的任务,例如更新 UI 和响应用户交互。这类任务应尽可能快地执行,以确保流畅的用户体验。
- User Initiated:
用于用户主动请求的任务,需要快速完成但不是立即需要的,例如打开文档或加载数据。
- Utility:
用于可能需要一些时间完成的任务,例如下载文件或处理数据。通常是用户可以等待完成的任务。
- Background:
用于对用户不可见的任务,例如同步数据、预取内容。这类任务优先级最低,在系统资源充足时执行。
- Default:
系统默认的 QoS 级别,通常介于 User Initiated 和 Utility 之间。
使用 QoS
在 GCD 中,可以在创建队列或提交任务时指定 QoS 级别:
Objective-C 示例
1
2
3
4
5
6
7
8
|
// 创建具有 User Initiated 级别的 QoS 的并发队列
dispatch_queue_attr_t myAttr = dispatch_queue_attr_make_with_qos_class(QOS_CLASS_USER_INITIATED, QOS_CLASS_DEFAULT, 0);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.myconcurrent", myAttr);
// 向 Background 级别的 QoS 的全局队列提交任务
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
NSLog(@"Task is executing");
});
|
在引入 QoS 类之前,Objective-C 中使用调度队列优先级来确定任务的执行顺序:
DISPATCH_QUEUE_PRIORITY_HIGH
: 高优先级。
DISPATCH_QUEUE_PRIORITY_DEFAULT
: 默认优先级。
DISPATCH_QUEUE_PRIORITY_LOW
: 低优先级。
DISPATCH_QUEUE_PRIORITY_BACKGROUND
: 后台优先级。
在现代 macOS 和 iOS 系统中,Apple 建议使用 QoS 类而不是调度队列优先级,因为 QoS 类提供了更细粒度的控制,并且更符合现代应用的需求。
Swift 示例
1
2
3
4
5
6
7
|
// 创建具有 User Initiated 级别的 QoS 的并发队列
let queue = DispatchQueue(label: "com.example.myqueue", qos: .userInitiated)
// 向 Background 级别的 QoS 的全局队列提交任务
DispatchQueue.global(qos: .background).async {
print("Task is executing")
}
|
DispatchWorkItem
DispatchWorkItem 是 Grand Central Dispatch (GCD) 提供的一个类,用于封装可以在队列中执行的工作单元。DispatchWorkItem 提供了更强大的功能和灵活性,可以用来控制和管理并发任务。它支持任务的取消、优先级和 QoS(Quality of Service),以及任务完成后的通知回调。效果上类似于 Objective-C 中的dispatch_block_t
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 创建一个 DispatchWorkItem
let workItem = DispatchWorkItem {
print("Work item is executing")
Thread.sleep(forTimeInterval: 2) // 模拟耗时任务
print("Work item finished")
}
// 提交任务到全局并发队列
DispatchQueue.global().async(execute: workItem)
// 取消任务
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
workItem.cancel()
print("Work item cancel requested")
}
// 设置完成后的通知回调
workItem.notify(queue: DispatchQueue.main) {
print("Work item has completed or was cancelled")
}
|
Objective-C 没有 DispatchWorkItem,但可以使用 dispatch_block_t 和相关的 GCD 函数实现类似的功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 创建一个 dispatch_block_t
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"Block is executing");
[NSThread sleepForTimeInterval:2]; // 模拟耗时任务
NSLog(@"Block finished");
});
// 提交任务到全局并发队列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
// 延迟1秒后取消任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_block_cancel(block);
NSLog(@"Block cancel requested");
});
// 设置完成后的通知回调
dispatch_block_notify(block, dispatch_get_main_queue(), ^{
NSLog(@"Block has completed or was cancelled");
});
|
notify
DispatchWorkItem 和 dispatch_block_t 中的 notify 是 GCD 提供的一个强大功能,能够在指定任务执行完成或被取消之后执行一个通知回调。
使用场景
- UI 更新:任务完成后在主线程中更新 UI。
- 任务依赖:确保一个任务在另一个任务完成后执行。
- 资源清理:任务完成后执行资源清理操作。
注意事项
- 队列选择:确保回调代码在适当的队列中执行,避免在非主线程中更新 UI。
- 任务取消:即使任务被取消,notify 回调也会被执行。因此,回调代码中应检查任务是否被取消。
终~