多线程
iOS 多线程方案技术报告
在 iOS 系统中,多线程方案按照封装抽象程度从低到高排列。开发者通常应优先选择高阶方案,因为它们提供了更好的内存管理和性能优化。
1. 方案综述与对比表
| 方案名称 | 技术基础 | 语言接口 | 抽象层级 | 线程生命周期管理 | 推荐程度 |
|---|---|---|---|---|---|
| Pthread | POSIX 标准 | C | 底层 | 程序员手动管理 | 极低(仅跨平台需求时使用) |
| NSThread | Mach 线程封装 | Obj-C / Swift | 中低层 | 程序员手动管理 | 低(特定监控/长驻线程使用) |
| GCD | 队列 & 线程池 | C / Swift | 中高层 | 系统自动管理 | 高(主流方案) |
| NSOperation | 基于 GCD 封装 | Obj-C / Swift | 高层(面向对象) | 系统自动管理 | 高(复杂依赖逻辑) |
| Swift Concurrency | 协同线程池 | Swift | 语言级 | 编译器/运行时管理 | 极高(现代开发首选) |
2. 核心方案深度解析
A. Pthread (POSIX Threads)
这是跨平台的 C 语言标准接口。
- 优点:跨平台移植性强。
- 缺点:API 原始且复杂,需要手动处理线程的创建、销毁、同步等,极易引发内存泄漏或死锁。
- 建议:在纯 iOS 开发中基本不再使用。
B. NSThread
对底层 Mach 线程的轻量级面向对象封装。
- 特点:每个 NSThread 对象对应一个实际的内核线程。
- 优点:可以直接控制线程属性(如优先级、堆栈大小、线程名称)。
- 缺点:仍需手动管理生命周期;在高并发下频繁创建/销毁线程会产生巨大开销。
- 使用场景:需要维护一个“长驻线程”(如网络监听轮询)或需要获取当前执行线程对象时。
C. GCD (Grand Central Dispatch)
Apple 推荐的基于“任务”和“队列”的多线程技术。
- 核心逻辑:开发者只需将任务(Block/Closure)提交到特定的队列(串行或并行),无需关心线程的创建和分配,系统会根据 CPU 负载自动调度。
- 优点:
- 性能极致:底层使用 C 语言编写,效率极高。
- 自动管理:利用线程池技术,避免频繁创建线程的开销。
- 功能丰富:支持延时执行 (asyncAfter)、信号量 (Semaphore)、任务组 (Group)、屏障任务 (Barrier) 等。
- 缺点:任务一旦提交到队列,难以取消或设置复杂的依赖关系。
D. NSOperation & NSOperationQueue
基于 GCD 的更高层封装,将任务抽象为“操作对象”。
- 核心逻辑:将任务封装成 NSOperation 对象,放入 NSOperationQueue 中执行。
- 优点:
- 依赖管理:可以轻松设置操作 A 必须在操作 B 完成后执行(addDependency)。
- 可取消性:支持对尚未执行的任务进行取消操作。
- 最大并发数:可以限制同时执行的线程数量。
- 状态监听:通过 KVO 监听 isExecuting、isFinished 等状态。
- 使用场景:复杂的后台下载任务管理、具有先后逻辑关系的业务流程。
E. Swift Concurrency (Async/Await)
Swift 5.5 引入的革命性方案,从语言语法层面解决并发问题。
- 特点:引入 async/await、Task、Actor 等概念。
- 优点:
- 代码可读性:将异步回调“扁平化”,消除“金字塔回声(Callback Hell)”。
- 编译时安全:通过 Actor 模型在编译阶段防止数据竞态(Data Race)。
- 性能优化:采用协同调度(Cooperative Threading),减少线程切换(Context Switch)开销。
- 建议:新项目及现代 Swift 开发的官方首选方案。
3. 选型建议总结
- 简单异步任务:首选 GCD。例如:异步下载图片后回到主线程更新 UI。
- 复杂业务流/下载管理器:首选 NSOperation。当你需要暂停、取消任务或设置 A->B->C 这种复杂依赖时,它最方便。
- 现代 Swift 项目:首选 Swift Concurrency。它不仅写起来爽,还能帮你规避绝大多数多线程安全漏洞。
- 性能监控/低层交互:偶尔涉及 NSThread,用于标识线程或特定的 RunLoop 管理。
GCD 基础知识
是什么
Grand Central Dispatch(GCD) 是 Apple 在 macOS 10.6(Snow Leopard)(发布于 2009-08-28)引入的一个 系统级并发编程框架,在后续的 iOS 4(发布于 2010-06-21)中将 GCD 下放到了移动端,统一了 Apple 全平台并发模型。后续的 NSOperation、Swift Concurrency 都是在 GCD 之上逐步演进的抽象。
GCD 是 Apple 为多核并行运算提供的 C 语言底层解决方案。它通过将线程管理的代码下沉到系统级,让开发者能够以“函数式”的思维处理并发。GCD 的核心库叫 libdispatch,最早由 Apple 内部和 FreeBSD 社区合作完成。深度依赖 Darwin 内核调度机制。后期 libdispatch 开源,成为 Swift Concurrency 的底层基础之一,Swift 的 async/await,最终仍然落在 GCD/内核线程池上。
首次面世的时间
Grand Central Dispatch (GCD) 首次面世的时间是 2009年8月28日,它作为 Mac OS X 10.6 Snow Leopard 操作系统的一部分正式推出
诞生的过程
一、时代背景:多核时代来得比软件准备得更快(2004–2008)
- CPU 发展路线发生根本性变化,在 2004 年前后,业界已经明确意识到:
- 单核频率提升遇到功耗与散热墙
- CPU 厂商转向:
- 多核(dual-core / quad-core)
- SMT / 超线程
- NUMA 架构
问题是:
操作系统和应用层的软件模型,仍然停留在“一个主线程 + 少量后台线程”的时代。
- Apple 面临的现实困境(非常关键)。在 2005–2008 年这段时间,Apple 同时面临几件“叠加危机”:
- Mac OS X 普及多核硬件
- Mac Pro、MacBookPro 快速进入双核、四核
- iPhone 项目已经启动(2005年)
- 极度受限的功耗
- UI 必须保持 100% 流畅
- 开发者的并能能力普遍不足
- pthread 难用,使用门槛较高
- 锁的滥用导致:死锁、优先级反转、UI 卡顿
- Mac OS X 普及多核硬件
Apple 内部非常清楚一件事:
如果并发模型继续停留在 pthread + lock 层面,多核红利根本吃不到,反而会制造灾难。
二、传统线程模型在 Apple 生态中的失败
pthread 模型的问题(不是不好,而是“不适合 UI 平台”)pthread 的核心假设是:
- 线程是“重量级资源”
- 程序员负责:
- 生命周期:创建、销毁、休眠等。。。
- 同步
- 调度策略
在服务器端尚可接受,但在 Apple 的场景中:
- UI 线程必须始终响应
- 后台任务数量不可预测
- 功耗必须可控
结果是:
- 线程数 = 程序员的认知极限
- 多核 ≠ 高并发
- 多数 App 实际只用到 1–2 个核
Cocoa / Objective-C 动态系统的矛盾
- Objective-C 运行时是高度动态的
- Cocoa API 并不是“默认线程安全”
- 锁一旦加错:性能雪崩、Bug 极其隐蔽
Apple 很早就意识到:
“把并发正确性外包给应用开发者”是行不通的。
三、GCD 的思想来源:不是凭空发明的
“任务(Task)而非线程(Thread)”思想早已存在。GCD 并不是第一个提出这些概念的系统。Apple 的创新点不在“是否首次提出”,而在于:
把这些思想整合进一个“系统级 API”,并强制推广到整个生态。
Apple 的关键判断(非常重要)
Apple 在内部做了一个非常激进的判断:并发不应该由“线程”作为抽象边界,而应该由“工作单元”作为抽象边界。
于是有了:
- block(闭包)作为任务载体
- queue(队列)作为调度语义
- 系统负责线程管理
这一步,直接否定了 pthread 时代的设计哲学。
四、GCD 在 2006–2008 年间的内部孕育
libdispatch 的内部孵化大致时间线(非官方,但符合 Apple 工程节奏):
- 2006 年左右
- Apple 内部开始实验性调度系统
- 与 Core OS 团队深度绑定
- 2007 年
- block 语法开始在 Clang 内部推进
- dispatch queue 原型出现
- 2008 年
- 与 Snow Leopard 项目强绑定
- GCD 成为系统级“卖点”
注意一点:
如果没有 block,GCD 是无法成立的。
block 是 GCD 的“语法基石”,两者是同时推进的。
五、2009 年发布:不是终点,而是“强制转向点”
2009 年 8 月 28 日的发布,本质上是:
Apple 对整个开发者生态的一次“并发模型强制迁移”。
此后发生的事实证明:
- pthread 在 App 层逐渐退居幕后
- NSOperationQueue 基于 GCD 重构
- Swift Concurrency 最终仍然落在 GCD 之上
六:一句高度概括
GCD 并不是为了“让并发更强”,而是为了“让普通开发者在多核时代不至于把系统写崩”。
它的出现,是:
- 多核硬件不可逆
- UI 平台容错率极低
- Apple 不信任开发者手写并发
这三点共同作用下的必然产物。
GCD 提供了什么能力?
我们已经知道,GCD 是一个系统级别的库、或者说底层的并发编程框架,那么查看它的头文件就能知道它都提供了什么功能。对于 iOS 应用开发者而言,理解 GCD 的底层机制和头文件结构至关重要,但这并不意味着需要掌握其提供的所有 API。由于 GCD 是一个系统级框架,其头文件中包含了大量用于系统内部、驱动程序或高级调试的接口。本指南将基于最新的 libdispatch 公开头文件列表(共 15 个),明确区分应用开发者必须掌握的核心 API 和无需掌握的底层/系统级 API。
可以在 Xcode 里通过 Command + 鼠标左键 点击 dispatch_async 函数或者 GCD 有关的其他函数,就能跳转到它的头文件里去了。。。这个应该都知道吧。。。或者使用 Command + Shift + O 然后输入 GCD 的相关函数 API 如 dispatch_async 同样可以进入。。。既然都看这种文章了,相信这些 Xcode 小技巧应该都知道了吧。。。
可以看到 dispatch 的头文件不多,仅 15 个。其中还有部分几乎没有任何 API 都是宏定义以及 dispatch.h 集合所有的头文件。
GCD 头文件结构与 API 分类(15 个公开头文件)
GCD 的公共头文件位于 <dispatch/ 目录下,它们共同定义了 GCD 的全部功能。通过分析这些头文件,我们可以将 GCD 的 API 划分为三个主要类别:核心应用层 API、进阶应用层 API 和系统级底层 API。
| 头文件 | 核心功能描述 | 开发者重要性 | 建议掌握程度 |
|---|---|---|---|
dispatch.h |
总头文件,包含所有其他公共头文件。 | 极高 | 了解(通过它引入所有 API) |
queue.h |
队列创建、任务派发(同步/异步)、主队列、全局队列。 | 极高 | 必须掌握 |
group.h |
任务组管理、等待、完成通知。 | 极高 | 必须掌握 |
semaphore.h |
信号量,用于资源访问控制和线程同步。 | 高 | 必须掌握 |
time.h |
时间常量和计算,用于延迟执行和超时设置。 | 高 | 必须掌握(配合 queue.h 和 group.h) |
block.h |
Block 相关的 API,如任务取消、等待。 | 中 | 推荐掌握 |
source.h |
调度源,用于处理系统事件(定时器、文件、信号等)。 | 中 | 进阶学习 |
once.h |
dispatch_once,用于单例模式。 |
中 | Obj-C 项目中掌握 |
workloop.h |
dispatch_workloop_t,优先级排序的特殊队列。 |
低 | 极少使用 |
data.h |
dispatch_data_t,用于高效处理内存块。 |
低 | 无需掌握 |
io.h |
dispatch_io_t,异步文件 I/O。 |
低 | 无需掌握 |
introspection.h |
运行时内省和调试工具 API。 | 低 | 无需掌握 |
object.h |
GCD 对象的引用计数和底层管理。 | 低 | 无需掌握 |
base.h |
基础宏定义、类型定义。 | 低 | 无需掌握 |
dispatch_swift_shims.h |
Swift 语言桥接层。 | 极低 | 无需掌握 |
日常的 iOS 应用开发基本只需要了解以下 6 个头文件提供的功能就足够了。
queue.h
这是 GCD 的核心。队列是任务的容器,任务派发决定了任务的执行方式(同步或异步)。
| API | 描述 | 关键用途 |
|---|---|---|
dispatch_queue_create |
创建自定义队列(串行或并发)。 | 实现线程安全(串行队列)或并发执行(并发队列)。 |
dispatch_get_global_queue |
获取系统提供的全局并发队列。 | 执行后台任务,如网络请求、数据处理。 |
dispatch_get_main_queue |
获取主队列(串行),绑定到主线程。 | 更新 UI,确保 UI 操作在主线程执行。 |
dispatch_async |
异步派发任务到指定队列,立即返回。 | 启动后台任务,保持主线程响应。 |
dispatch_sync |
同步派发任务到指定队列,阻塞当前线程直到任务完成。 | 线程间数据同步,等待结果。(注意:避免在主队列同步派发,否则会导致死锁) |
dispatch_barrier_async |
异步派发一个屏障任务到并发队列。 | 实现高效的“多读单写”模式,保证数据安全。 |
异步函数只能保证任务的执行和提交不在同一个函数调用栈内,并不能保证一定会在不同的线程,这一点很多人可能不理解或者误解了异步。如:
异步主队列时,任务的提交和执行都是在主线程,但是提交任务时的函数调用栈并不会立即执行任务,这个任务的执行要依赖主线程的运行循环(RunLoop)的下一个循环周期。
同步函数看似没有作用,但其实在多线程环境中,用来实现线程同步是非常合适的解决方案。
最常用的场景
1 | dispatch_queue_t queue = dispatch_get_global_queue(0, 0); |
需要注意的问题
底层实现原理
group.h
任务组用于监控一组任务的完成状态,是实现多个异步任务完成后执行统一回调的关键。
| API | 描述 | 关键用途 |
|---|---|---|
dispatch_group_create |
创建一个新的任务组。 | 组织一组相关的异步任务。 |
dispatch_group_async |
将任务与队列、任务组关联并异步派发。 | 启动任务并自动将其计入任务组。 |
dispatch_group_enter / dispatch_group_leave |
手动增加/减少任务组中的未完成任务计数。 | 用于非 dispatch_group_async 方式启动的任务,实现手动计数。 |
dispatch_group_notify |
注册一个回调,在任务组中所有任务完成后执行。 | 统一处理多个异步任务的结果,常用于回到主线程更新 UI。 |
dispatch_group_wait |
同步等待任务组完成,可设置超时时间。 | 阻塞当前线程,直到所有任务完成或超时。 |
semaphore.h
信号量是一种低级同步机制,用于控制对有限资源的并发访问数量。
| API | 描述 | 关键用途 |
|---|---|---|
dispatch_semaphore_create |
创建信号量,并设置初始计数值。 | 限制并发数(如并发下载数)或实现线程同步。 |
dispatch_semaphore_wait |
信号量减 1,如果结果小于 0 则阻塞当前线程。 | 尝试获取资源,如果资源不足则等待。 |
dispatch_semaphore_signal |
信号量加 1,如果有等待的线程则唤醒一个。 | 释放资源。 |
time.h
时间 API 主要用于计算相对时间或绝对时间,常与 dispatch_after 或 dispatch_group_wait 配合使用。
| API | 描述 | 关键用途 |
|---|---|---|
dispatch_time |
创建一个相对时间(基于当前时间或参考时间)。 | 计算延迟时间,如 dispatch_time(DISPATCH_TIME_NOW, delayInNanoseconds)。 |
DISPATCH_TIME_NOW / DISPATCH_TIME_FOREVER |
时间常量。 | 表示当前时间或无限期等待。 |
source.h
调度源允许开发者监听系统底层事件,如文件描述符、Mach 端口、信号、定时器等。对于应用开发者,最常用的是定时器 (DISPATCH_SOURCE_TYPE_TIMER)。
还有 dispatch_after(),虽然 dispatch_after() 的函数声明是写在 queue.h 文件中的,但是它的实现却是在 source.c 中的,而且使用的正是 dispatch_source。
once.h
dispatch_once 确保一个代码块在应用程序的生命周期内只被执行一次,常用于实现线程安全的单例模式。
注意: 在 Swift 中,由于语言特性(如静态属性的延迟初始化),
dispatch_once已经废弃,不再需要手动调用。但在维护旧的 Objective-C 代码时,它仍然是实现单例的标准方式。
162:
死锁?
什么是死锁?
什么情况下产生死锁?
1 | dispatch_queue_t queue = dispatch_get_main_queue(); |
以上代码如果是在主线程执行就会死锁。如果不在主线程执行不会产生死锁。
1 | dispatch_queue_t queue = dispatch_get_main_queue(); |
以上代码不会产生死锁,异步主队列是在主线程上执行任务。很经典的场景。
1 | NSLog(@"执行任务1"); |
以上代码会产生死锁,两个block任务在同一个队列,又是同步函数
1 | NSLog(@"执行任务1"); |
以上代码不会产生死锁,
1 | NSLog(@"执行任务1"); |
以上代码也不会产生死锁。
1 | NSLog(@"执行任务1"); |
以上代码也不会产生死锁,并发队列
使用同步函数 sync 往当前的串行队列再次添加任务就会产生死锁
往当前串行队列同步派发任务就导致死锁
如果当前队列是串行队列,同步派发任务到当前这个队列会导致死锁
165:
面试题
以下代码的打印结果是什么:
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
打印结果是 1,3。performSelector: withObject: afterDelay: 方法依赖 RunLoop,子线程的 RunLoop 默认不开启,导致 2 的打印并不会执行。
166:
performSelector:withObject:afterDelay: 是在 CoreFoundation 库里面实现的
performSelector:withObject: 是在 objc4 库里面实现的
GNUstep 将 Cocoa 的 Objective-C 库重写了一遍并开源了,所以可以参考学习。
167:
面试题2
以下代码的打印结果是什么:
1 | - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { |
打印结果是 1,然后程序崩溃,打印完 1 之后线程退出了,没办法执行 test 了,要保活子线程就得开启 RunLoop,强引用也解决不了。
168:
调度组的使用
实现功能:发送多个网络请求,所有网络请求都完成的时候处理事情?
正是调度组的典型使用场景
1 | dispatch_group_t group = dispatch_group_create(); |
线程同步方案
169:
多线程存在的问题?同时做多件事情提高了效率,但是存在线程安全问题
资源共享问题:
多个线程访问和使用同一个资源
案例:
存钱取钱。
卖票案例。
解决办法:
线程同步技术:加锁
170-173:
iOS 中的线程同步方案有:
- OSSpinLock
- os_unfair_lock
- pthread_mutex
- NSLock
- NSRecursiveLock
- NSCondition
- NSConditionLock
- @synchronized
- dispatch_semaphore
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
然后是介绍 OSSpinLock 的使用了。导入 libkern/OSAtomic.h 头文件,OS_SPINLOCK_INIT 宏创建锁,OSSpinLockLock() 函数加锁 OSSpinLockUnlock() 函数解锁
OSSpinLock 叫自旋锁,等待锁的期间一直占用着 CPU,已经过时了,还有可能产生优先级问题。。。总是就是不要用了,那为什么还讲那么多七七八八的呢
原来 172 里是对代码的结构进行了调整。。。结合了一些封装设计在里面。。。站在讲解多线程锁的角度来讲,完全没有必要讲这块内容!
但是我想想有没有必要学习下怎么写的???
173:答疑
174:os_unfair_lock
这是自旋锁还是互斥锁呢?好像没讲呢?根据汇编得知是互斥锁,调用syscall让CPU休眠了
175:pthread_mutex
互斥锁,等待锁的线程会处于休眠状态。
176:pthread_mutex 递归锁
177:自旋锁,互斥锁的汇编实现
自旋锁底层是一个循环一直在执行。
互斥锁底层调用了syscall让CPU进入休眠
178:pthread_mutex 条件
讲了个什么场景没太理解。。。
感觉讲的真不好,总是磕磕绊绊来来回回重复叙述
pthread_cond_wait()
pthread_cond_signal()
意思好像是始终能保证 A 在 B 之前执行?好像是 OperationQueue 里面的依赖关系的?可能就是依赖的底层实现?
生产者?消费者?模式,意思是生产者必须先生产产品出来才可以给消费者消费。
179:
NSLock 是对 mutex 普通锁的封装
NSRecursiveLock 是对 mutex 递归锁的封装
NSCondition 是 mutex cond 的封装
180:课后答疑
181:
[self.confition signal] 解释
完全听不明白了,不知道在表达啥呢。。。
答疑 RunLoop,这不是前面讲过了,看看什么问题
- NSThread 的生命不是一个强引用就能保证的,它的 RunLoop 不执行,线程的入口函数执行完就释放了。
- 线程的入口函数执行了一段代码,但是如果不实现循环,它就跟普通的命令行程序执行完入口函数就结束了。
其实还是对线程的理解不够深入。确实线程对象比普通的对象更抽象。
182:NSConditionLock
NSConditionLock?不是有一个 NSCondition 吗,这个又是什么
原来 NSConditionLock 是对 NSCondition 的封装。提供了更多功能
条件锁,应用场景还是建立依赖。。。
183:串行队列
是的,GCD 的串行队列也可以实现线程同步
1 | dispatch_queue_t moneyQueue = dispatch_queue_create("custom_queue1", DISPATCH_QUEUE_SERIAL); |
这个好理解
184-185:dispatch_semaphore
信号量,通过信号量控制线程最大并发数量为 1 的话同样可以实现线程同步。
dispatch_semaphore_wait() 函数会让信号量减 1。如果信号量大于 0 会执行后续代码。如果信号量 <= 0 会进入休眠直到大于 0 才可以继续执行后续代码。
dispatch_semaphore_signal() 会让信号量加 1。
186:@synchronized 编译器语法糖
是 pthread_mutex_t 的封装。最简单的同步方案。
源码是在 objc4 库的 objc-sync.h objc-sync.mm 文件中,objc_sync_entry() objc_sync_exit()。发现是对 pthread_mutex_t 递归锁的封装。
187:
总结线程同步方案:
性能从高到底
- os_unfair_lock
- OSSpinLock
- dispatch_semaphore
- pthread_mutex
- dispatch_queue(DISPATCH_QUEUE_SERIAL)
- NSLock
- NSCondition
- pthread_mutex(recursive)
- NSRecursiveLock
- NSConditionLock
- @synchronized
188:自旋锁,互斥锁对比
- 什么情况使用自旋锁比较划算?
- 预计线程等待锁的时间很短
- 加锁的代码(临界区)经常被调用,但竞争情况很少发生
- CPU 资源不紧张
- 多核心处理器
- 什么情况使用互斥锁比较划算
- 预计线程等待锁的时间较长
- 单核处理器
- 加锁的代码有 IO 操作
- 加锁的代码复杂,循环量大
- 加锁的代码竞争频繁
189:atomic
属于属性的修饰符,但在 iOS 中只推荐使用 nonatomic。atomic 更多用于 macOS 中。atomic 的作用是使属性的 getter 和 setter 是原子的,意思是属性的 setter 方法和 getter 方法都进行了加锁。
源码在 objc4 中。objc-accessors.mm 中,可以看到 atomic 的情况下进行了加锁。
但是又说没有办法保证使用属性的线程是安全的?这是什么意思
这个还是需要 AI 再详细解读一下了
问清楚为什么在 iOS 中不推荐使用 atomic。
190-192:读写安全
文件的读写安全的实现。
- 信号量最大并发为1,可以实现,但是只能单读单写。如果要实现多读单写就不能使用信号量了。多个线程可以同时读取数据,但只有一个线程写入数据,读和写是不能同时进行(只能有一个线程写,且写的时候无法读,可以有多个线程读但读的时候无法写)
- pthread_rwlock 读写锁,等待锁的线程会进入休眠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pthread_rwlock_t lock;
pthread_rwlock_init(&lock, NULL);
// 读锁
pthread_rwlock_rdlock(&lock);
// ......
pthread_rwlock_unlock(&lock);
// 写锁
pthread_rwlock_wrlock(&lock);
// ......
pthread_rwlock_unlock(&lock);
// 释放锁
pthread_rwlock_destroy(&lock); - dispatch_barrier_async 异步栅栏函数
1
2
3
4
5
6
7
8// 必须使用创建的并发队列,全局并发队列或串行队列都无效
dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 读操作...
});
dispatch_barrier_async(queue, ^{
// 写操作...
});
