# Dispatch(GCD)苹果官方文档 白话文讲解(Objective‑C 版本文档)

GCD 全称:Grand Central Dispatch ,苹果多核多线程技术,底层 C 语言写的。OC 的 API 就是 C 函数;Swift 的DispatchQueue是对这套 C 接口的面向对象封装。 文档地址:developer.apple.com/documentati...

一、GCD 到底是干嘛的(大白话)

以前写多线程用NSThread,手动创建线程、管理线程、销毁线程,很麻烦,还容易开一大堆线程把系统资源耗光。

GCD 核心思想:把任务(要干的活)放到队列里,由系统来管理线程。你只管提交任务,线程创建、复用、销毁全部交给操作系统处理,不用你手动管。

两个基础概念一定要分清:

  1. 任务(Block) :你要执行的代码块,比如计算、读写文件、网络解析;

  2. 队列(dispatch_queue_t) :存放任务的排队容器,FIFO 先进先出。

    • 串行队列:一次只跑 1 个任务;做完一个再取下一个。
    • 并发队列:同时可以跑多个任务,多核 CPU 会同时执行多个 block。

⚠️重点:队列≠线程!队列只是存放任务;系统从线程池拿线程出来执行队列里的任务。同一个队列的任务不一定固定跑在同一个线程。

二、队列的 4 种来源(OC 写法)

1. 主队列 dispatch_get_main_queue()

ini 复制代码
dispatch_queue_t mainQ = dispatch_get_main_queue();
  • 串行队列,绑定主线程(UI 线程)
  • 所有 UI 刷新任务必须交给主队列。
  • ❌禁止在主队列用dispatch_sync,直接死锁。

2. 系统全局并发队列 dispatch_get_global_queue

ini 复制代码
// 参数1:优先级;参数2保留参数永远传0
dispatch_queue_t globalQ = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);

系统自带的并发队列池,优先用这个,不要自己随便新建并发队列。 QoS 优先级(从高到低)

  • QOS_CLASS_USER_INITIATED 用户操作触发,需要快点出结果(下拉刷新、列表加载)
  • QOS_CLASS_UTILITY 耗时持续任务,下载、同步数据
  • QOS_CLASS_BACKGROUND 后台默默干活,清理缓存、日志归档,优先级最低

3. 手动创建串行队列

arduino 复制代码
// label:调试用标签,方便Xcode看栈;属性传NULL代表串行
dispatch_queue_t serialQ = dispatch_queue_create("com.my.serial", NULL);

用途:保证任务顺序、解决多线程读写冲突。 官方建议:自定义串行队列最好设置target指向全局队列,避免大量新建队列造成线程压力。

scss 复制代码
dispatch_set_target_queue(serialQ, dispatch_get_global_queue(QOS_CLASS_UTILITY,0));

4. 手动创建并发队列

ini 复制代码
dispatch_queue_t concurrentQ = dispatch_queue_create("com.my.concurrent", DISPATCH_QUEUE_CONCURRENT);

⚠️官方提醒:尽量少自己创建并发队列。并发队列多了会让系统生成大量线程,耗尽 app 线程资源。优先用系统 global 全局队列。

三、提交任务两种方式:异步 async / 同步 sync

1)dispatch_async 异步(90% 业务用这个)

less 复制代码
// 把任务丢进队列,函数立刻返回,不卡住当前代码;任务在别的线程执行
dispatch_async(globalQ, ^{
    //子线程:做耗时计算
    dispatch_async(dispatch_get_main_queue(), ^{
        //切回主线程刷新UI
    });
});

真实场景:下载图片、解析大量数据,子线程干活,干完切主线程刷新界面。

2)dispatch_sync 同步,阻塞当前线程,等 block 全部做完函数才返回

less 复制代码
dispatch_sync(serialQ, ^{
    //执行任务
});
// 代码走到这里代表block已经全部执行完

⚠️致命坑:

less 复制代码
// 千万不要写!死锁!当前就在主线程,同步往主队列提交任务,互相等待卡死
dispatch_sync(dispatch_get_main_queue(), ^{});

使用场景:很少,一般用于读共享资源,线程安全读取;不要在主线程使用 sync。

四、高级组件

1. dispatch_after 延时执行(OC)

不是定时器,只是 "到时间把 block 丢进队列",不会循环。

scss 复制代码
// 2秒后提交到主队列执行
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"2秒后执行");
});

2. dispatch_group_t 调度组:等待一堆异步任务全部结束

场景:同时发起 3 个下载,全部完成之后再统一回调。

scss 复制代码
dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);
dispatch_async(globalQ, ^{
    //任务1
    dispatch_group_leave(group);
});

dispatch_group_enter(group);
dispatch_async(globalQ, ^{
    //任务2
    dispatch_group_leave(group);
});

//所有任务完成之后,通知回调到主队列
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"全部任务结束,刷新UI");
});

enter 和 leave 必须成对,多 leave / 少 leave 都会 bug。

3. dispatch_barrier_async 屏障(读写锁)

专门给自己创建的并发队列使用。

  • 前面所有任务跑完,才执行 barrier 的 block;
  • barrier 执行期间,队列其他任务全部卡住;
  • barrier 结束之后,后续任务再继续并发跑。
scss 复制代码
dispatch_queue_t myConcurrentQ = dispatch_queue_create("com.rw", DISPATCH_QUEUE_CONCURRENT);

//普通并发读任务
dispatch_async(myConcurrentQ, ^{ /*读*/ });

//屏障写操作:等待前面全部读完,再执行写;写的期间其他任务等待
dispatch_barrier_async(myConcurrentQ, ^{
    //写数据,保证同一时间只有这一个写操作
});

注意:不能用在系统 global 全局并发队列上,barrier 会失效!只能用自己 create 出来的并发队列。

4. dispatch_apply 并行循环(Swift 叫 concurrentPerform)

多核并行跑循环,会阻塞当前线程直到全部循环结束。适合大量批量计算。

scss 复制代码
//循环跑100次,多核并行
size_t count = 100;
dispatch_apply(count, globalQ, ^(size_t i) {
    NSLog(@"迭代 %zu",i);
});
//走到这里代表全部循环执行完毕

五、内存与生命周期(OC 重点)

GCD 对象(队列、group、workitem)属于引用计数管理。

  • MRC 环境下:dispatch_retain() / dispatch_release()
  • ARC 下:系统自动管理,不需要手动 retain/release。

六、官方文档重点警告(踩坑汇总,白话文)

  1. 不要任务内部阻塞线程 如果队列里面的 block 做 sleep、同步 IO 等阻塞操作,线程被卡住,系统会不断新建更多线程来完成队列剩下任务,线程数量爆炸,app 性能暴跌。
  2. 不要随便新建并发队列 每一个并发队列都会占用线程资源,大量dispatch_queue_create(...,DISPATCH_QUEUE_CONCURRENT)会耗尽线程池。尽量使用dispatch_get_global_queue全局队列。

如果需要串行任务:创建串行队列,dispatch_set_target_queue挂靠到 global 全局队列,既保证串行,又不会额外消耗线程。

  1. 分清DISPATCH_TIME_NOW(开机后的相对时间)和DISPATCH_WALLTIME_NOW(系统墙上时钟时间)。手机休眠,相对时间会暂停;墙上时钟跟随系统真实时间。
  2. 死锁最常见来源:同一个队列内部调用dispatch_sync。比如串行队列 A,在 A 的 block 里面调用dispatch_sync(A,^{...}),互相等待直接死锁。

七、Swift <-> OC 简单对照,方便你切换

表格

功能 Objective‑C Swift(DispatchQueue)
主队列 dispatch_get_main_queue() DispatchQueue.main
全局并发队列 dispatch_get_global_queue(QOS_CLASS_UTILITY,0) DispatchQueue.global(qos:.utility)
异步提交 dispatch_async(q,block) q.async { }
同步提交 dispatch_sync(q,block) q.sync { }
延时 dispatch_after asyncAfter(deadline:)
调度组 dispatch_group_t DispatchGroup
并行循环 dispatch_apply DispatchQueue.concurrentPerform

你之前写的 Swift 瀑布流后台计算,底层就是这套 C‑API,Swift 只是一层封装。

八、业务选型快速口诀(OC 版本)

  1. UI 操作:丢dispatch_get_main_queue()
  2. 耗时计算、文件解析:dispatch_async提交 global 全局队列;
  3. 需要任务按顺序执行:自定义串行队列,记得 set_target 挂靠 global 队列;
  4. 多个异步任务全部做完再处理结果:用dispatch_group_t
  5. 多读单写:自建并发队列 + dispatch_barrier_async做写屏障;
  6. 大量循环计算加速:dispatch_apply
  7. 永远不要在主队列执行dispatch_sync,避免死锁。
相关推荐
七牛开发者1 小时前
Coding Agent 如何跑稳长任务?从上下文管理到运行时状态
前端·javascript·后端
七牛开发者1 小时前
拆解 DeepSeek Harness:Profile 与 Bundle 如何装配运行时
前端·javascript·后端
九九落2 小时前
北京时间校准怎么做?电脑和手机时间不准的排查方法
javascript
念何架构之路3 小时前
Gin响应渲染
前端·javascript·gin
WebInfra4 小时前
Rspack 2.2 发布:30+ 项性能优化,拥抱 Solid 2.0
前端·javascript·前端框架
伯远医学5 小时前
RIP-seq 高分文献案例分享
java·前端·javascript·人工智能·算法·eclipse·html
Hilaku5 小时前
Web Components 为什么火不起来?
前端·javascript·程序员
vancece5 小时前
JS实现堆 & 215.数组中的第K个最大元素
java·前端·javascript
qq_316837756 小时前
uniapp 安卓/H5 使用 contentEditable 属性 富文本编辑器
android·javascript·uni-app