iOS开发基础36-多线程之GCD
GCD 深度解析:队列与任务、同步异步、栅栏函数、队列组与单例模式
本文系统梳理 GCD(Grand Central Dispatch)并发编程框架:任务与队列核心概念、同步/异步执行、栅栏函数、并发/串行队列创建、四种组合效果、线程间通信、延时/一次性/快速迭代/队列组/挂起恢复/GCD 定时器等高级用法,以及单例模式实现。
一、GCD 基本概念
1. 简介
GCD(Grand Central Dispatch)是一套基于 C 语言的底层并发 API,用于任务的分发与执行。
优势:
- 多核优化:自动利用设备多核 CPU,线程数由系统根据核心数和负载动态管理。
- 自动线程管理:自动创建、调度、销毁线程,开发者无需手动管理线程生命周期。
- 简化代码:只需定义任务(block)并添加到队列,无需编写复杂线程管理代码。
2. 任务与队列
GCD 两个核心概念:
- 任务(block):需要执行的具体操作。
- 队列(dispatch queue):存放任务的容器,GCD 按 FIFO(先进先出)原则取出任务并放到对应线程执行。
FIFO 指任务取出的顺序。并发队列中任务取出后并发执行,执行完成顺序不一定是 FIFO;串行队列中任务取出后顺序执行,完成顺序即 FIFO。
使用两步:
- 定制任务(确定执行的操作);
- 将任务添加到队列(GCD 自动调度执行)。
二、任务执行方式
1. 同步与异步
| 执行方式 | 函数 | 是否开启新线程 | 是否阻塞当前线程 |
|---|---|---|---|
| 同步(sync) | dispatch_sync |
否(在当前线程执行) | 是(等待任务完成) |
| 异步(async) | dispatch_async |
是(具备开启新线程能力) | 否(不等待,立即返回) |
// 同步执行(当前线程等待任务完成)
dispatch_sync(queue, ^{
NSLog(@"同步任务");
});
// 异步执行(不等待,可能开启新线程)
dispatch_async(queue, ^{
NSLog(@"异步任务");
});
异步在串行队列中也可能不开启新线程(GCD 内部优化),但具备开启能力;在并发队列中通常开启新线程并发执行。
2. 栅栏函数(dispatch_barrier)
栅栏函数在自定义并发队列中,保证前面的任务全部执行完后才执行栅栏任务,栅栏任务完成后才执行后面的任务,适用于"多读单写"场景:
dispatch_queue_t queue = dispatch_queue_create("com.example.rw", DISPATCH_QUEUE_CONCURRENT);
// 读操作(并发)
dispatch_async(queue, ^{ NSLog(@"读1"); });
dispatch_async(queue, ^{ NSLog(@"读2"); });
// 写操作(栅栏:前面的读全部完成后才执行写,写完成后才执行后面的读)
dispatch_barrier_async(queue, ^{
NSLog(@"写操作(独占)");
});
// 后续读操作
dispatch_async(queue, ^{ NSLog(@"读3"); });
栅栏函数注意事项:
- 在自定义并发队列中有效;
- 在全局并发队列中,
dispatch_barrier_async行为等同于dispatch_async(不栅栏),因为全局队列被系统共享,不能被栅栏阻塞;- 在串行队列中无意义(串行队列本来就是顺序执行);
- 同步栅栏
dispatch_barrier_sync会阻塞当前线程。
三、队列类型与创建
1. 队列类型
| 队列类型 | 执行方式 | 说明 |
|---|---|---|
| 并发队列(Concurrent) | 多任务同时执行 | 任务取出后并发执行,自动开启多条线程 |
| 串行队列(Serial) | 任务顺序执行 | 一个任务完成后才执行下一个,只开启一条线程 |
2. 创建队列
// 自定义并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrent", DISPATCH_QUEUE_CONCURRENT);
// 自定义串行队列(NULL 等同于 DISPATCH_QUEUE_SERIAL)
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serial", DISPATCH_QUEUE_SERIAL);
队列标签(label)用于调试,建议使用反向域名格式,不影响功能。
3. 全局并发队列
系统提供的全局并发队列,按服务质量(QoS)分优先级:
// 获取全局并发队列(iOS 8+ 推荐 QoS 方式)
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
| QoS 优先级 | 说明 |
|---|---|
QOS_CLASS_USER_INTERACTIVE |
用户交互(最高,动画、事件响应) |
QOS_CLASS_USER_INITIATED |
用户发起(用户明确请求,需立即结果) |
QOS_CLASS_DEFAULT |
默认 |
QOS_CLASS_UTILITY |
工具类(耗时操作,进度可见) |
QOS_CLASS_BACKGROUND |
后台(最低,用户不可见,如备份、同步) |
iOS 8 之前使用
DISPATCH_QUEUE_PRIORITY_HIGH/DEFAULT/LOW/BACKGROUND,iOS 8+ 映射到对应 QoS,仍可使用但推荐 QoS 常量。
4. 主队列
GCD 自带的特殊串行队列,任务在主线程执行,用于更新 UI:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
四、同步/异步与并发/串行的组合
| 组合 | 是否开启新线程 | 执行方式 |
|---|---|---|
| 同步 + 并发队列 | 否(当前线程) | 当前线程顺序执行任务(不并发) |
| 同步 + 串行队列 | 否(当前线程) | 当前线程顺序执行任务 |
| 异步 + 并发队列 | 是(多条新线程) | 多任务并发执行 |
| 异步 + 串行队列 | 是(一条新线程) | 任务在新线程顺序执行 |
主队列死锁:在主线程中调用
dispatch_sync(dispatch_get_main_queue(), ^{...})会导致死锁——主线程等待 sync 任务完成,而 sync 任务在主队列中等待主线程执行,互相等待。同步执行主队列任务必须在非主线程中调用。
五、线程间通信
耗时操作在子线程执行,完成后回到主线程更新 UI:
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
// 子线程:耗时操作(网络请求、文件处理、图片解码)
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
// 主线程:更新 UI
self.imageView.image = image;
});
});
六、GCD 高级用法
1. 延时执行
// 2 秒后在主队列执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
NSLog(@"2 秒后执行");
});
dispatch_after不是精确的定时器,是在指定时间后将任务添加到队列,任务执行时间取决于队列状态。
2. 一次性执行(dispatch_once)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 整个程序生命周期内只执行一次,线程安全
NSLog(@"只执行一次");
});
dispatch_once线程安全,是实现单例的标准方式。Swift 中static let常量天然线程安全,无需dispatch_once。
3. 快速迭代(dispatch_apply)
// 并发执行 10 次迭代(index 从 0 到 9)
// dispatch_apply 是同步的,会阻塞当前线程直到所有迭代完成
dispatch_apply(10, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^(size_t index) {
NSLog(@"迭代 %zu", index);
});
NSLog(@"所有迭代完成");
dispatch_apply在并发队列中迭代并发执行,但函数本身同步阻塞当前线程,直到所有迭代完成。适合在后台线程中处理可并行的循环任务。
4. 队列组(dispatch_group)
基本用法:group_async + notify
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"任务1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"任务2");
});
// 所有任务完成后,在主队列执行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成");
});
// 或阻塞等待所有任务完成(慎用,会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
手动管理:enter/leave(用于 block 内的异步操作)
当任务 block 内部还有异步操作(如网络请求),dispatch_group_async 无法感知内部异步完成,需用 enter/leave 手动管理:
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group); // 进入组(任务开始)
[self loadDataWithCompletion:^{
NSLog(@"数据加载完成");
dispatch_group_leave(group); // 离开组(任务完成)
}];
dispatch_group_enter(group);
[self loadImageWithCompletion:^{
NSLog(@"图片加载完成");
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"数据和图片都加载完成");
});
enter和leave必须成对出现,leave次数不能超过enter,否则崩溃。
5. 队列挂起与恢复
// 挂起队列(暂停执行新任务,正在执行的任务不受影响)
dispatch_suspend(queue);
// 恢复队列
dispatch_resume(queue);
dispatch_suspend是异步的,只影响后续取出的任务,不影响正在执行的任务。挂起和恢复必须成对调用。
6. GCD 定时器(dispatch_source_t)
GCD 定时器比 NSTimer 更精准,不依赖 RunLoop,在子线程也可执行:
@property (nonatomic, strong) dispatch_source_t timer;
- (void)startTimer {
// 创建定时器源(DISPATCH_SOURCE_TYPE_TIMER)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
// 设置定时器:起始时间、间隔(秒)、误差(0 为最精准)
dispatch_source_set_timer(self.timer,
dispatch_time(DISPATCH_TIME_NOW, 0),
1.0 * NSEC_PER_SEC,
0);
// 设置回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"定时器触发");
});
// 启动定时器(GCD 源默认是挂起的,必须 resume 才开始)
dispatch_resume(self.timer);
}
- (void)stopTimer {
if (self.timer) {
dispatch_cancel(self.timer);
self.timer = nil;
}
}
GCD 定时器创建后默认挂起,必须调用
dispatch_resume才开始执行。取消后不可恢复,需重新创建。
七、单例模式
1. 作用
程序运行期间保证某个类只有一个实例,提供全局访问点,常用于共享资源(网络管理器、数据管理器)。
2. ARC 单例实现
@interface DataManager : NSObject <NSCopying>
+ (instancetype)sharedManager;
@end
@implementation DataManager
static id _instance;
+ (instancetype)sharedManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
// 重写 allocWithZone,防止通过 alloc 创建新实例
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
// 遵循 NSCopying 时重写 copyWithZone,返回同一实例
- (id)copyWithZone:(NSZone *)zone {
return _instance;
}
@end
如果类不遵循
NSCopying,则无需实现copyWithZone。如需支持mutableCopy,需遵循NSMutableCopying并实现mutableCopyWithZone:。
八、Swift 版本对照
队列与任务
// 主队列
let main = DispatchQueue.main
// 全局队列
let global = DispatchQueue.global()
let userInteractive = DispatchQueue.global(qos: .userInteractive)
// 自定义串行队列
let serial = DispatchQueue(label: "com.example.serial")
// 自定义并发队列
let concurrent = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)
// 异步执行
global.async {
print("耗时操作")
DispatchQueue.main.async {
print("回主线程更新 UI")
}
}
// 同步执行(注意:主队列同步会死锁)
serial.sync {
print("同步任务")
}
栅栏函数
let queue = DispatchQueue(label: "com.example.rw", attributes: .concurrent)
queue.async { print("读1") }
queue.async { print("读2") }
queue.async(flags: .barrier) {
print("写操作(独占)")
}
queue.async { print("读3") }
延时执行
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("2 秒后执行")
}
一次性代码(Swift 中 static let 天然线程安全)
final class DataManager {
static let shared = DataManager()
private init() {}
}
快速迭代
DispatchQueue.concurrentPerform(iterations: 10) { index in
print("迭代 \(index)")
}
队列组
let group = DispatchGroup()
let queue = DispatchQueue.global()
queue.async(group: group) { print("任务1") }
queue.async(group: group) { print("任务2") }
group.notify(queue: .main) {
print("所有任务完成")
}
// 手动 enter/leave
group.enter()
loadData {
print("数据完成")
group.leave()
}
group.notify(queue: .main) { print("完成") }
GCD 定时器
var timer: DispatchSourceTimer?
func startTimer() {
timer = DispatchSource.makeTimerSource(queue: .main)
timer?.schedule(deadline: .now(), repeating: 1.0)
timer?.setEventHandler {
print("定时器触发")
}
timer?.resume() // 必须 resume 才开始
}
func stopTimer() {
timer?.cancel()
timer = nil
}
单例
final class DataManager {
static let shared = DataManager()
private init() {} // 禁止外部创建
}
九、总结
- 核心概念:任务(block)+ 队列(dispatch queue),GCD 按 FIFO 取出任务并调度执行,自动管理线程生命周期。
- 同步/异步:同步在当前线程执行并阻塞等待;异步不等待,具备开启新线程能力。
- 并发/串行:并发队列多任务同时执行(自动开多线程);串行队列任务顺序执行(单线程)。
- 栅栏函数:
dispatch_barrier_async在自定义并发队列中实现多读单写;全局队列中无效(等同普通 async),串行队列中无意义。 - 队列类型:主队列(串行,主线程)、全局并发队列(QoS 分 5 档)、自定义串行/并发队列。
- 主队列死锁:主线程中
dispatch_sync主队列任务会死锁。 - 线程通信:全局队列异步执行耗时操作 → 主队列异步更新 UI。
- 高级用法:
dispatch_after延时、dispatch_once一次性(单例)、dispatch_apply快速迭代(同步阻塞)、dispatch_group队列组(async/notify/enter/leave/wait)、dispatch_suspend/resume挂起恢复、dispatch_source_tGCD 定时器(比 NSTimer 精准)。 - 单例:ARC 中
dispatch_once+ 重写allocWithZone+copyWithZone(遵循 NSCopying 时);Swift 中static let shared天然线程安全。

浙公网安备 33010602011771号