了解 Swift 中的 DispatchQueue Barrier 如何使用及常用场景

前言

在 iOS 中,我们可以使用 Barrier 来对并发队列中的一个或者多个任务进行同步调度。也就是说,它允许你将线程不安全的对象转换为线程安全的对象。它为在并发调度队列中执行的代码块创建一个同步点。

派发 Barrier 任务是在处理并发队列时充当串行队列样式对象的一组函数。使用 GCD的 Barrier API 可以确保提交的任务是指定队列在特定时间内唯一执行的任务。

这意味着在调度 Barrier 任务之前提交到队列的所有任务必须在 Barrier 任务执行之前完成。当轮到 Barrier 任务时,GCD 确保队列在此期间不执行任何其他任务。当 Barrier 任务完成后,队列会返回到其默认实现。GCD 提供同步和异步 Barrier 函数。

代码示例

假设现在你有三项任务,需求是任务一和三的代码可以并发执行,任务二的项目需要串行执行,且任务二需要在任务一执行完再执行,任务三需要任务二执行完再执行。示例代码如下:

php 复制代码
let concurrentQueue = DispatchQueue(label: "com.demo.dispatchBarrier", attributes: .concurrent)
for i in 1...5 {
    concurrentQueue.async {
        print("task 1, \(i)")
    }
}
print("task 1 add complete")

for i in 1...5 {
    concurrentQueue.async(flags: .barrier, execute: {
        print("task 2, \(i)")
    })
}
print("task 2 add complete")

for i in 1...5 {
    concurrentQueue.async {
        print("task 3,\(i)")
    }
}

print("task 3 add complete")

上面代码的 log 如下:

csharp 复制代码
task 1 add complete
task 1, 2
task 2 add complete
task 1, 1
task 2 add complete
task 1, 4
task 1, 3
task 1, 5
task 2, 1
task 2, 2
task 2, 3
task 2, 4
task 2, 5
task 3,1
task 3,3
task 3,4
task 3,5
task 3,2

由于三项任务我们都是异步添加到队列的,添加后会立即返回,不会阻塞主队列。所以看到三项任务的 add complete 在 task 1 还未执行完的时候就打印了。

根据任务执行的打印信息可以看到,任务一和任务三的执行顺序并不是升序的,这说明了任务一和任务三确实是并发执行的。而任务二的执行顺序是升序的,而且任务二的所有输出都是在任务一之后,任务三的所有输出在任务二之后。

注意事项:根据 Apple 的官方文档说明,如果你想使用 Barrier 的话,你指定的队列必须是你自己调用 dispatch_queue_create 创建的并发队列。如果你指定串行队列或者 global 并行队列的话,其效果和 dispatch_async 函数效果一致。

使用场景

一般需要使用到 GCD Barrier 的场景就是多读单写:

  • 可以允许读操作并发。
  • 读操作和写操作同一时间只能执行一个。
  • 写操作和写操作同一时间只能执行一个。

示例代码如下:

less 复制代码
class ReadWriteLockByBarrier {
    private let queue: DispatchQueue
    init() {
       queue = DispatchQueue(label: "com.demo.readWriteLock", qos: .default, attributes: .concurrent, autoreleaseFrequency: .workItem, target: nil)
    }
    
    deinit { }
    
    func read<T>(_ closure: () -> T) -> T {
        queue.sync {
            closure()
        }
    }
    
    func write(_ closure: @escaping () -> Void) {
        queue.async(flags: .barrier) {
            closure()
        }
    }
}

pthread_rwlock_rdlock / pthread_rwlock_wrlock

多读单写的技术实现方案还有另外一种,就是使用 pthread_rwlock_rdlock / pthread_rwlock_wrlock 来实现,具体示例代码如下:

scss 复制代码
class ReadWriteLock {
    private var rwlock = pthread_rwlock_t()

    init() {
        pthread_rwlock_init(&rwlock, nil)
    }

    deinit {
        pthread_rwlock_destroy(&rwlock)
    }

    func read<T>(_ closure: () -> T) -> T {
        pthread_rwlock_rdlock(&rwlock)
        defer { pthread_rwlock_unlock(&rwlock) }
        return closure()
    }

    func write(_ closure: () -> Void) {
        pthread_rwlock_wrlock(&rwlock)
        defer { pthread_rwlock_unlock(&rwlock) }
        closure()
    }
}

读写锁提供了一种有效的方法来处理对共享资源的并发访问。它们允许多个线程同时读取数据,同时确保一次只有一个线程可以写入资源。这会提高性能,因为读取操作不会阻塞其他读取操作,从而减少了线程之间的争用。

读写锁通常提供两种类型的锁:

  • 读锁:这些锁可以由多个线程同时获取,允许它们同时读取共享资源。读锁不会阻塞其他读锁,但会阻止获取写锁。
  • 写锁:这些锁提供对共享资源的独占访问,阻止其他线程获取读锁和写锁。写锁确保一次只有一个线程可以修改资源,从而防止数据不一致。

总结

  • GCD Barrier 可以在并发队列中提供一个时机来执行同步任务。
  • GCD Barrier 指定的队列必须是自己自定义的并发队列,否则效果和 dispatch_async 没有区别。
  • 读写锁的两种实现方法:GCD Barrier 和 pthread_rwlock_rdlock / pthread_rwlock_wrlock。
相关推荐
问道飞鱼2 分钟前
【移动端知识】移动端多 WebView 互访方案:Android、iOS 与鸿蒙实现
android·ios·harmonyos·多webview互访
mascon1 小时前
U3D打包IOS的自我总结
ios
名字不要太长 像我这样就好1 小时前
【iOS】继承链
macos·ios·cocoa
karshey2 小时前
【IOS webview】IOS13不支持svelte 样式嵌套
ios
潜龙95272 小时前
第4.3节 iOS App生成追溯关系
macos·ios·cocoa
游戏开发爱好者811 小时前
iOS App 电池消耗管理与优化 提升用户体验的完整指南
android·ios·小程序·https·uni-app·iphone·webview
神策技术社区18 小时前
iOS 全埋点点击事件采集白皮书
大数据·ios·app
wuyoula19 小时前
iOS V2签名网站系统源码/IPA在线签名/全开源版本/亲测
ios
2501_9159184119 小时前
iOS 性能监控工具全解析 选择合适的调试方案提升 App 性能
android·ios·小程序·https·uni-app·iphone·webview
fishycx19 小时前
iOS 构建配置与 AdHoc 打包说明
ios