了解 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。
相关推荐
2501_916008895 小时前
手机抓包app大全:无需root的安卓抓包软件列表
android·ios·智能手机·小程序·uni-app·iphone·webview
胖虎111 小时前
iOS 如何全局修改项目字体
ios·hook·ios字体·字体适配·ios字体适配
songgeb12 小时前
iOS App进入后台时会发生什么
ios
笑尘pyrotechnic13 小时前
运行,暂停,检查:探索如何使用LLDB进行有效调试
ios·objective-c·lldb
metaRTC14 小时前
webRTC IPC客户端React Native版编程指南
react native·react.js·ios·webrtc·p2p·ipc
ajassi200017 小时前
开源 Objective-C IOS 应用开发(十八)音频的播放
ios·开源·objective-c
2501_9159214317 小时前
Windows 系统下的 IPA 加密工具实战指南,如何在非 macOS 环境完成 IPA 混淆、加固与工程化处理
android·windows·macos·ios·小程序·uni-app·iphone
马拉萨的春天17 小时前
iOS的分类中为什么不能添加变量以及如何设置关联对象的弱引用效果
ios·分类·数据挖掘
ajassi200018 小时前
开源 Objective-C IOS 应用开发(十七)CAF音频的录制
ios·开源·objective-c
源码君miui520861 天前
JAVA国际版同城服务同城信息同城任务发布平台APP源码Android + IOS
android·java·ios