1-15.【GCD】barrier 在底层是如何实现“读写互斥”的?

3 阅读2分钟

1️⃣ 目标:读多写少的安全策略

假设我们有一个 共享资源,需要:

  • 多个读可以同时执行 → 并发,提高性能
  • 写操作必须独占 → 保证数据一致性

dispatch_barrier 就是实现这个模式的工具。


2️⃣ 队列模型回顾

  • 串行队列:一次只允许一个任务执行

  • 并发队列:允许多个任务同时执行

  • 自定义并发队列

    • GCD 内部维护了 任务队列 + 当前执行计数(running count)
    • target queue 是 global queue 或线程池

3️⃣ barrier 的底层原理

核心概念

dispatch_barrier 本质是 队列层的屏障控制

  1. 队列维护一个 当前正在执行任务的计数器runningexecutingCount

  2. 当 barrier 任务到达队列:

    • 队列检查当前 running count

      • 如果 count > 0 → barrier 等待所有前置任务完成
      • 如果 count == 0 → barrier 可以立即执行
  3. barrier 执行期间:

    • 队列 暂停派发后续任务
    • 任何新的任务也不能执行,直到 barrier 完成
  4. barrier 完成:

    • 队列恢复派发剩余任务
    • 剩余任务可以并发执行(如果是并发队列)

队列状态机示意

并发队列:
   队列头: [read1, read2, barrier, read3, read4]

执行计数 running = 0

1️⃣ read1 执行 -> running = 1
2️⃣ read2 执行 -> running = 2
3️⃣ barrier 到队列头 -> 等待 running = 0
4️⃣ read1, read2 完成 -> running = 0
5️⃣ barrier 执行 -> running = 1 (独占)
6️⃣ barrier 完成 -> running = 0
7️⃣ read3, read4 开始并发执行 -> running = 2
  • Barrier 期间 running = 1 → 独占
  • Barrier 前/后 → 并发读 allowed

4️⃣ 关键技术点

  1. FIFO 队列保证顺序
  • Barrier 永远在队列头执行前,队列保证前面的任务完成
  • 这样读操作不会和 barrier 写操作冲突
  1. 执行计数 + 派发控制
  • running count + 队列内部状态机
  • barrier 期间暂停队列派发新任务
  • barrier 完成后恢复并发执行
  1. 不依赖锁或互斥量
  • 这是 GCD 高性能的原因
  • barrier 依赖队列内部状态 + 信号通知

5️⃣ 实际使用示例

let queue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
var sharedArray = [Int]()

// 并发读
queue.async { print(sharedArray.count) }
queue.async { print(sharedArray.first ?? 0) }

// barrier 写
queue.async(flags: .barrier) {
    sharedArray.append(42)
}

// barrier 后的读
queue.async { print(sharedArray.last ?? 0) }
  • barrier 确保写操作独占
  • barrier 前后读操作可以并发执行
  • 无需手动加锁

6️⃣ 小结

Barrier 的底层实现机制

  1. 队列内部维护 执行计数 + 状态机

  2. Barrier 提交时:

    • 等待前置任务完成
    • 执行期间独占队列(暂停后续任务)
  3. Barrier 完成后:

    • 恢复队列派发
    • 后续任务并发执行

本质上,dispatch_barrier自定义并发队列上的“FIFO + 计数屏障”机制,保证了读多写少的互斥安全,而不依赖显式锁。