了解 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。
相关推荐
zzialx32 分钟前
iOS:如何使用OpenVC库计算照片相似度
macos·ios·cocoa
卡尔特斯16 小时前
Flutter 监听当前页面可见与隐藏状态
android·flutter·ios
SunshineBrother1 天前
Flutter性能优化细节
android·flutter·ios
小周同学:1 天前
Fiddler抓取App接口-Andriod/IOS配置方法
前端·ios·fiddler
刘小哈哈哈1 天前
iOS实现一个强大的本地状态记录容器
ios
werch2 天前
兼容移动端ios,安卓,web端底部软键盘弹出,输入框被遮挡问题
android·前端·ios
CocoaKier2 天前
Xcode16默认不再支持iOS15以下系统真机调试
ios·xcode
圆觉妙心2 天前
解决 iOS日志在 Windows 电脑显示
windows·ios
SunshineBrother2 天前
flutter 流(Stream)介绍&结合RxDart使用
android·flutter·ios
恋猫de小郭2 天前
腾讯 TDF 即将开源 Kuikly 跨端框架,Kotlin 支持全平台
android·flutter·ios