了解 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 小时前
UniApp集成极光推送详细教程
android·ios·uni-app·极光推送
ii_best6 小时前
iOS 按键越狱脚本支持一键新机软件教程
ios
lilili啊啊啊8 小时前
查看iphone手机的使用记录-克魔实战
ios·智能手机·iphone
鸿蒙布道师9 小时前
鸿蒙NEXT开发随机工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师1 天前
鸿蒙NEXT开发Base64工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
美狐美颜sdk1 天前
美颜SDK兼容性挑战:如何让美颜滤镜API适配iOS与安卓?
android·深度学习·ios·美颜sdk·第三方美颜sdk·视频美颜sdk
Invisible_He1 天前
iOS自定义collection view的page size(width/height)分页效果
ui·ios·swift·collection
小橙子20771 天前
一条命令配置移动端(Android / iOS)自动化环境
android·ios·自动化
yidahis1 天前
iOS启动优化 - 1分钟让你的启动速度降低 1s
ios·客户端
帅次1 天前
Flutter DropdownButton 详解
android·flutter·ios·kotlin·gradle·webview