如何线程安全地存档一组自定义对象?

时间:2017-12-30 13:35:51

标签: swift multithreading collections thread-safety archiving

我想要使用Set<CostumObject>存档NSKeyedArchiver类型的实例。

假设customObject1: CostumObjectcustomObject2: CostumObject在某处实例化。

如果我使用以下声明:

let setOfCostomObjects: Set<CostumObject> = [customObject1, customObject2]
let data = NSKeyedArchiver.archivedData(withRootObject: setOfCostomObjects)

NSKeyedArchiver按顺序归档两个自定义对象,其属性以递归方式存档。

这不是线程安全的,因为另一个线程可以在归档期间改变自定义对象及其属性。

我认为我可以线程安全地归档自定义对象的每个属性,因此允许并发获取只允许一个集合,通过使用带有屏障的并发队列来设置如下:

private let concurrentPropertyAccessQueue = DispatchQueue(label: "concurrentPropertyAccessQueue", attributes: .concurrent)
…
private var safeProperty = CostumProperty.init()
public private(set) var property: CostumProperty {
  get {
    var result = CostumProperty.init()
    concurrentPropertyAccessQueue.sync { result = safeProperty } // sync, because result is returned
    return result
  } // get
  set { concurrentPropertyAccessQueue.async(flags: .barrier) { safeProperty = newValue } // executes locked after all gets
  } // set
}  
…
public func threadSafeArchiveOfProperty() -> Data {
    var data = Data.init()
    concurrentPropertyAccessQueue.sync {  // sync, because result is returned
      data = NSKeyedArchiver.archivedData(withRootObject: self.safeProperty) 
    }
    return data
}

我想我也可以用类似的方式线程安全地归档整个自定义对象:

private let concurrentObjectAccessQueue = DispatchQueue(label: "concurrentObjectAccessQueue", attributes: .concurrent)
…
public func encode(with aCoder: NSCoder) {
    concurrentObjectAccessQueue.async(execute: {
        aCoder.encode(self.property forKey: "property")
        …
    })
}

问题仍然是,如何线程安全地存档自定义对象集 这将要求在归档期间锁定对该组元素的写访问。

这样做的一种方法可能是定义全局并发队列:

public let globalConcurrentAccessQueue = DispatchQueue(label: "globalConcurrentAccessQueue", attributes: .concurrent)  

要在归档期间锁定集及其所有元素,可以编写一个Set类型的扩展名,以定义func threadSafeArchiveOfSet(),如上所述。
然后,此函数将覆盖Set的encode(with aCoder: NSCoder),以便globalConcurrentAccessQueue被锁定。

这是正确的方法吗? 我认为这是一个应该有标准解决方案的标准问题。

2 个答案:

答案 0 :(得分:1)

通常,属性级同步根本就不合适。它提供对各个属性的线程安全访问,但是它不能确保对不同属性之间可能存在相互依赖性的更广泛对象的线程安全访问。原型示例是具有名和姓属性的Person对象。分别对名字和姓氏进行同步更改仍然可以在内部不一致状态下捕获对象。您经常需要在更高级别同步对象,如果这样做,它将呈现属性级同步redundent。

一些不相关的观察结果:

  1. encode方法必须同步执行其任务,而不是异步执行。调用者假定编码在返回时完成。我可以猜到为什么你可能让它异步(例如它毕竟没有明确地返回任何东西),但问题不在于是否返回任何东西,而是更广泛地说是否有任何副作用。同步对象。在这种情况下(您正在更新NSCoder对象),因此您必须在sync中使用encode

  2. 有几次你使用初始化变量的模式,调用sync来修改该局部变量,然后返回该值。 E.g。

    func threadSafeArchiveOfProperty() -> Data {
        var data = Data.init()
        concurrentPropertyAccessQueue.sync {  // sync, because result is returned
            data = NSKeyedArchiver.archivedData(withRootObject: self.safeProperty) 
        }
        return data
    }
    

    但是sync提供了一种很好的简化方法,即如果闭包返回一个值,sync也会返回它。如果闭包只有一行,你甚至不需要在闭包中明确return

    func threadSafeArchiveOfProperty() -> Data {
        return concurrentPropertyAccessQueue.sync {  // sync, because result is returned
            NSKeyedArchiver.archivedData(withRootObject: self.safeProperty) 
        }
    }
    

答案 1 :(得分:0)

Basem Emara描述了here线程安全数组的解决方案,它也可以应用于集合:

他宣布SynchronizedArray模仿常规数组。其中包含一个私有并发队列和数组,并公开了几个数组的属性和方法 不可变访问是同步和同时完成的,而可变访问是与屏障异步完成的,即在队列中的所有其他块终止后。