因此,我的代码使用并发调度队列和屏障标志来支持并行读取,但在写入时阻止读取/写入:

struct SafeDict<Element> {
    private var dict = [String: Element]()
    private var queue = DispatchQueue(label: "WriteQueue", attributes: .concurrent)
    
    func get(_ key: String) -> Element? {
        queue.sync { return dict[key] }
    }
    
    mutating func set(_ key: String, value: Element) {
        queue.sync(flags: .barrier) { dict[key] = value }
    }
}


var safeDict = SafeDict<Int>()
for i in 1...4 {
    DispatchQueue.global().async {
        switch i {
        case 1:
            safeDict.get("one")
        case 2:
            safeDict.set("one", value: 1) // Waits for get (good)
        case 3:
            safeDict.get("one") // Runs after set in parallel
        case 4:
            safeDict.get("two") // Runs after set in parallel
        default:
            print("done")
        }
    }
}

但由于参与者函数是异步的,并行读取将相互等待.如何才能干净利落地避免这种情况呢?

我能想到的一种替代方法是不使用参与者,而是使用两个异步方法(设置).而GET将等待一项既定的任务,如果不是零的话.但这似乎太乏味了.

actor SafeDictActor<Element> {
    private var dict = [String: Element]()
    
    func get(_ key: String) -> Element? { dict[key] }
    
    func set(_ key: String, value: Element) { dict[key] = value }

}

let safeDictActor = SafeDictActor<Int>()
for i in 1...4 {
    Task {
        switch i {
        case 1:
            await safeDictActor.get("one")
        case 2:
            await safeDictActor.set("one", value: 1) // Waits for get (good)
        case 3:
            await safeDictActor.get("one") // waits for set (good)
        case 4:
            await safeDictActor.get("two") // waits for previous get (bad)
        default:
            print("done")
        }
    }
}

推荐答案

正如Paulw 11在对最近一个问题的 comments 中提到的,How to solve reader/writer problem with swift concurrency?(你在你的问题中也注意到了),参与者使用一种基本的同步机制,这种机制本质上与读写器模式不兼容.Actor基于一种简单(但优雅)的机制,通过避免对共享状态的任何并行访问来防止竞争,但Reader-Writer基于允许并行读取的模式.读者-作者并行阅读的思想与参与者的基本机制完全不兼容.


话虽如此,我想补充几点意见:

  1. 如果你真的想要阅读器和写入器(我不知道你为什么要这样做;参见下面的第三点),你可以坚持使用GCD来实现这个目标.是的,我们通常避免将GCD与SWIFT并发代码库混合在一起,但这是可以做到的(特别是如果将GCD划分为单一类型,而不是在单一类型中混合不同的技术堆栈).除非绝对必要,否则我一般不会建议这么做,但这是可以做到的.

    但如果将其与SWIFT并发代码库集成,您可能会考虑将此类型符合@unchecked Sendable.SWIFT并发采用了代码线程安全的编译时验证(特别是将"严格并发"的构建设置设置为"完成";有效地预览了我们将在SWIFT 6中享受的那种判断).@unchecked Sendable有效地让编译器知道您是在保证该对象的线程安全性.它让您的对象在SWIFT并发性中发挥得很好.

    关于Sendable的更多信息,WWDC 2022的S视频Eliminate data races using Swift Concurrency是关于这个话题的很好的入门读物.

  2. 我注意到您的读取器-写入器实现使用的是值类型:但值类型的整个思想是为线程安全提供一种简单的机制,从而 for each 线程提供其自己的对象副本,从而消除潜在的竞争.但读取器-写入器的思想是允许从多个线程对同一对象进行线程安全的共享访问.现在,您可以有一个读取器-写入器值类型,但是说您既想要线程安全访问以跨线程共享对象,又想 for each 线程提供自己的副本,这有点奇怪.在某些情况下,您可能会这样做,但许多读写用例需要共享可变状态,这就需要引用语义.(顺便说一句,您考虑的参与者实现也使用了引用语义.)

  3. 我理解读者-作者模式的直觉吸引力.但在我的经验中,基于GCD的实现通常无法实现其promise .在我的所有基准测试中,(A)它的性能仅略高于GCD序列队列;(B)它通常比简单锁慢得多;以及(C)它经常带来比它解决的问题更多的问题.例如,如果像你想象的那样,你有unfair lock0个阅读量,那么就会有一个更深层次的线程爆炸问题,而读者-作者只会让这个问题变得更加复杂.根据我的经验,GCD的开销可能开始超过并行执行的潜在好处.我曾试图构建现实场景,让读者-作家的速度比简单的unfair lock更快,但都是徒劳的.我建议对您的特定用例进行基准测试,避免假设读写器会更快.

    归根结底,演员对于高级别的线程安全/完整性非常重要.在性能很关键的地方(例如,Knuth设想的3%的情况下,性能确实很关键,比如计算密集型并行算法),我个人跳过读写器,跳到性能更高的地方(例如,不公平的锁).但对于大多数实际用例来说,参与者就足够了.

  4. 你说过:

    但由于参与者函数是异步的,并行读取将相互等待.如何才能干净利落地避免这种情况呢?

    我能想到的一个替代方案是不使用actor,而是使用2个bloc方法(get set).而get将等待一个设定的任务,如果不是nil的话.但这似乎太乏味了.

    actor SafeDictActor<Element> {
        private var dict = [String: Element]()
        
        func get(_ key: String) -> Element? { dict[key] }
        
        func set(_ key: String, value: Element) { dict[key] = value }
    }
    

    我将把您建议的"不使用参与者"放在一边,然后向我们展示一个参与者实现.我想你的意思是"用一个演员"我也要把get不是setasync的说法放在一边,因为他们不是.(是的,调用时需要await个,但它们是参与者的同步函数.)

    但是,一位演员确实优雅地为我们同步了这一点.不需要GCD队列或锁.只需简单地将其设为actor分,您就完成了.但你是正确的,两个get呼叫不能并行运行.参与者将一次执行一个功能.这就是它确保线程安全的方式.

    对于绝大多数情况,这个简单的参与者实现已经足够了.但对于那些关键的3%的情况,其中性能确实是最重要的考虑因素,只有到那时我们才会考虑其他模式.

Ios相关问答推荐

在SwiftData中从@ Query创建可排序、有序和分组的数据

如何加密字符串使用AES. GCM在Dart和解密相同的Swift?

iOS中的分段拾取器—手柄点击已 Select 的项目

DriverKit驱动程序中可能存在IOBufferMemoyDescriptor泄漏

如何在wiftUI中管理导航栈?

SwiftUI Divider过大了其父视图

如何在SwiftUI中为带有CornerRadius的矩形创建下边框?

确保方法调配的安全有效实施

Flutter iOS 键盘问题:. TextInputType.number 中缺少字符

仅更改 AttributedString 的字符串(例如 UIButton 配置 attributeTitle)

保存和读取登录到 keys 串不工作 IOS swift

如何解决错误 HE0004:无法在 Visual Studio 2022 中加载框架ContentDeliveryServices

Flutter 构建 ios 失败:运行 pod 安装时出错

使用 AVCaptureDeviceTypeBuiltInTripleCamera 时 Select 合适的相机进行条码扫描

如何解码没有名称的 JSON 数组?

撤销 Apple 登录令牌以进行帐户删除过程

SwiftUI:添加核心数据项时更新选项卡栏徽章

如何在 UILabel 中找到文本子字符串的 CGRect?

自动布局:是什么创建了名为 UIView-Encapsulated-Layout-Width & Height 的约束?

在自动布局中居中子视图的 X 会引发未准备好约束