【问题标题】:Create thread safe array in Swift在 Swift 中创建线程安全数组
【发布时间】:2015-03-27 06:48:31
【问题描述】:

我在 Swift 中遇到了线程问题。我有一个包含一些对象的数组。通过委托,该类大约每秒都会获得新对象。之后我必须检查对象是否已经在数组中,所以我必须更新对象,否则我必须删除/添加新对象。

如果我添加一个新对象,我必须首先通过网络获取一些数据。这是通过块处理的。

现在我的问题是,如何同步这些任务?

我尝试了一个 dispatch_semaphore,但是这个会阻塞 UI,直到阻塞完成。

我还尝试了一个简单的 bool 变量,它检查块当前是否正在执行并同时跳过比较方法。

但这两种方法都不理想。

管理数组的最佳方法是什么,我不想在数组中有重复的数据。

【问题讨论】:

  • 如果您不想在 Array 中出现重复数据,请使用 Set

标签: ios arrays swift multithreading read-write


【解决方案1】:

我解决这个问题的方法是使用串行调度队列来同步对盒装数组的访问。当您尝试获取索引处的值并且队列真的很忙时,它会阻塞线程,但这也是锁的问题。

public class SynchronizedArray<T> {
    private var array: [T] = []
    private let accessQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_SERIAL)

    public func append(newElement: T) {
        dispatch_async(self.accessQueue) {
            self.array.append(newElement)
        }
    }

    public subscript(index: Int) -> T {
        set {
            dispatch_async(self.accessQueue) {
                self.array[index] = newValue
            }
        }
        get {
            var element: T!

            dispatch_sync(self.accessQueue) {
                element = self.array[index]
            }

            return element
        }
    }
}

var a = SynchronizedArray<Int>()
a.append(1)
a.append(2)
a.append(3)

// can be empty as this is non-thread safe access
println(a.array)

// thread-safe synchonized access
println(a[0])
println(a[1])
println(a[2])

【讨论】:

  • 你可以考虑使用读写模式:使用DISPATCH_QUEUE_CONCURRENT;将写入从 dispatch_async 更改为 dispatch_barrier_async;但留下阅读为dispatch_sync。这为您提供了并发读取,但写入仍然是同步的。
  • dispatch_barrier_async 仅用于写入。但是请阅读dispatch_sync。因此,虽然写入不会(也不应该)与其他任何事情同时发生,但读取可以与其他读取同时发生。请参阅 WWDC 2012 视频 Asynchronous Design Patterns with Blocks, GCD, and XPC 后半部分中的模式 #6。
  • 如何为自定义类型传递排序?
  • DispatchQueue 不是要求 CPU 将上下文切换到另一个线程,从而导致非常昂贵的操作吗?或者是sync()实际上解决了这个问题,因为它是只读资源访问,如果没有必要则不分派到队列?
【解决方案2】:

Swift 更新

线程安全访问的推荐模式是使用 dispatch barrier:

let queue = DispatchQueue(label: "thread-safe-obj", attributes: .concurrent)

// write
queue.async(flags: .barrier) {
    // perform writes on data
}

// read
var value: ValueType!
queue.sync {
    // perform read and assign value
}
return value

【讨论】:

  • 谢谢,但是 objc_sync_enter 与 dispatch_queue 的优缺点是什么?
  • objc_sync_enter 是 Objective-C 的@synchronized 下面的内容。因为我还没有真正看过objc_sync_enter 下面的内容,所以我有点相信开源代码会按承诺工作。至于使用串行队列,您确切地知道它是如何工作的(代码块顺序)。我猜想为每个线程安全的集合类型实例创建和管理一个串行队列的缺点可能会很昂贵。
  • 你让我很好奇,所以我在objc_sync_enter:rykap.com/objective-c/2015/05/09/synchronized找到了这个
  • Array 等值类型上使用 objc_sync_enter 是不安全/不正确的。 objc_sync_array 进行指针比较,并且不能保证桥接 Swift 值类型的指针的生命周期是稳定的(事实上大多数时候它不稳定稳定)。
【解决方案3】:

我认为 dispatch_barriers 值得研究。对我来说,使用 gcd 进行同步比使用 synchronize 关键字来避免来自多个线程的状态突变更直观。

https://mikeash.com/pyblog/friday-qa-2011-10-14-whats-new-in-gcd.html

【讨论】:

    【解决方案4】:

    一个小细节:在 Swift 3 中(至少在 Xcode 8 Beta 6 中),队列的语法发生了显着变化。 @Kirsteins 答案的重要变化是:

    private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess")
    
    txAccessQueue.async() {
      // Your async code goes here...
    }
    
    txAccessQueue.sync() {
      // Your sync code goes here...
    }
    

    【讨论】:

      【解决方案5】:

      这里有一个很好的答案,它是线程安全的并且不会阻塞并发读取:https://stackoverflow.com/a/15936959/2050665

      它是用 Objective C 编写的,但移植到 Swift 是微不足道的。

      @property (nonatomic, readwrite, strong) dispatch_queue_t thingQueue;
      @property (nonatomic, strong) NSObject *thing;
      
      - (id)init {
        ...
          _thingQueue = dispatch_queue_create("...", DISPATCH_QUEUE_CONCURRENT);
        ...
      }
      
      - (NSObject *)thing {
        __block NSObject *thing;
        dispatch_sync(self.thingQueue, ^{
          thing = _thing;
        });
        return thing;
      }
      
      - (void)setThing:(NSObject *)thing {
        dispatch_barrier_async(self.thingQueue, ^{
          _thing = thing;
        });
      }
      

      感谢https://stackoverflow.com/users/97337/rob-napier

      【讨论】:

        【解决方案6】:

        Kirsteins 的答案是正确的,但为了方便起见,我已经用 Amol Chaudhari 和 Rob 的建议更新了这个答案,即使用具有异步屏障的并发队列来允许并发读取但阻止写入。

        我还封装了一些对我有用的其他数组函数。

        public class SynchronizedArray<T> {
        private var array: [T] = []
        private let accessQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_CONCURRENT)
        
        public func append(newElement: T) {
            dispatch_barrier_async(self.accessQueue) {
                self.array.append(newElement)
            }
        }
        
        public func removeAtIndex(index: Int) {
            dispatch_barrier_async(self.accessQueue) {
                self.array.removeAtIndex(index)
            }
        }
        
        public var count: Int {
            var count = 0
        
            dispatch_sync(self.accessQueue) {
                count = self.array.count
            }
        
            return count
        }
        
        public func first() -> T? {
            var element: T?
        
            dispatch_sync(self.accessQueue) {
                if !self.array.isEmpty {
                    element = self.array[0]
                }
            }
        
            return element
        }
        
        public subscript(index: Int) -> T {
            set {
                dispatch_barrier_async(self.accessQueue) {
                    self.array[index] = newValue
                }
            }
            get {
                var element: T!
        
                dispatch_sync(self.accessQueue) {
                    element = self.array[index]
                }
        
                return element
            }
        }
        }
        

        更新 这是相同的代码,为 Swift3 更新。

        public class SynchronizedArray<T> {
        private var array: [T] = []
        private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess", attributes: .concurrent)
        
        public func append(newElement: T) {
        
            self.accessQueue.async(flags:.barrier) {
                self.array.append(newElement)
            }
        }
        
        public func removeAtIndex(index: Int) {
        
            self.accessQueue.async(flags:.barrier) {
                self.array.remove(at: index)
            }
        }
        
        public var count: Int {
            var count = 0
        
            self.accessQueue.sync {
                count = self.array.count
            }
        
            return count
        }
        
        public func first() -> T? {
            var element: T?
        
            self.accessQueue.sync {
                if !self.array.isEmpty {
                    element = self.array[0]
                }
            }
        
            return element
        }
        
        public subscript(index: Int) -> T {
            set {
                self.accessQueue.async(flags:.barrier) {
                    self.array[index] = newValue
                }
            }
            get {
                var element: T!
                self.accessQueue.sync {
                    element = self.array[index]
                }
        
                return element
            }
        }
        }
        

        【讨论】:

        • removeAtIndex 可能会删除此代码中的错误项目,导致 idx Cold 被其他线程 'removeAtIndex' 更改...
        • @Speakus 可能是错的,但我很确定它不会。它是异步的,但与其他数组操作在同一个并发队列中......所以 removeAtIndex 将在任何其他读取或写入操作发生之前发生。
        • @JordanSmith 当您调用 removeAtIndex 时会发生此问题,但代码将切换到另一个线程,例如当您获得 self.accessQueue 但在 .async 运行之前。
        • @mooney 我认为@Speakus 就在这里:假设同步数组只有一个元素,这不会失败吗? if synchronizedArray.count == 1 { synchronizedArray.remove(at: 0) } 这是一个竞争条件,假设两个线程执行该语句。两者同时读取计数为 1,同时将写入块排入队列。写块顺序执行,第二个将失败。我错了吗?这是设计者错误还是用户错误?无论哪种情况,它都非常脆弱,让我永远不想接触多线程......你怎么能保证这个安全?回到阅读和写作的排他锁?
        • @rmooney - 在您的 Swift 3 和 4 示例中,您可以简化其中一些例程,消除隐式展开的选项,例如var first: T? { return accessQueue.sync { self.array.first } }
        【解决方案7】:

        方法:

        使用DispatchQueue进行同步

        参考:

        http://basememara.com/creating-thread-safe-arrays-in-swift/

        代码:

        下面是一个线程安全数组的粗略实现,你可以微调它。

        public class ThreadSafeArray<Element> {
            
            private var elements    : [Element]
            private let syncQueue   = DispatchQueue(label: "Sync Queue",
                                                    qos: .default,
                                                    attributes: .concurrent,
                                                    autoreleaseFrequency: .inherit,
                                                    target: nil)
            
            public init() {
                elements = []
            }
            
            public init(_ newElements: [Element]) {
                elements = newElements
            }
            
            //MARK: Non-mutating
            
            public var first : Element? {
                return syncQueue.sync {
                    elements.first
                }
            }
            
            public var last : Element? {
                return syncQueue.sync {
                    elements.last
                }
            }
            
            public var count : Int {
                
                return syncQueue.sync {
                    elements.count
                }
            }
            
            public subscript(index: Int) -> Element {
                
                get {
                    return syncQueue.sync {
                        elements[index]
                    }
                }
                
                set {
                    syncQueue.sync(flags: .barrier) {
                        elements[index] = newValue
                    }
                }
            }
            
            public func reversed() -> [Element] {
                
                return syncQueue.sync {
                
                    elements.reversed()
                }
            }
            
            public func flatMap<T>(_ transform: (Element) throws -> T?) rethrows -> [T]  {
                
                return try syncQueue.sync {
                
                   try elements.flatMap(transform)
                }
            }
            
            public func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
                
                return syncQueue.sync {
                 
                    elements.filter(isIncluded)
                }
            }
            
            //MARK: Mutating
            
            public func append(_ element: Element) {
            
                syncQueue.sync(flags: .barrier) {
                    
                    elements.append(element)
                }
            }
            
            public func append<S>(contentsOf newElements: S) where Element == S.Element, S : Sequence {
                
                syncQueue.sync(flags: .barrier) {
                    
                    elements.append(contentsOf: newElements)
                }
            }
            
            public func remove(at index: Int) -> Element? {
        
                var element : Element?
        
                syncQueue.sync(flags: .barrier) {
                    
                    if elements.startIndex ..< elements.endIndex ~= index {
                        element = elements.remove(at: index)
                    }
                    else {
                        element = nil
                    }
                }
                
                return element
            }
        }
        
        extension ThreadSafeArray where Element : Equatable {
            
            public func index(of element: Element) -> Int? {
                
                return syncQueue.sync {
                    elements.index(of: element)
                }
            }
        }
        

        【讨论】:

        • 你使用的是哪个版本的 Swift ?
        • 我的错,我没有尝试编译代码就写了评论。我虽然 return queue.sync { _array } 不会编译,因为 sync 什么都不返回。
        • 返回数组,对数组的访问是同步的。
        • 对数组的访问是同步的,是的,但是对返回的数组执行的任何操作都不是。
        【解决方案8】:

        To improve the accepted answer 我建议使用 defer:

        objc_sync_enter(array)
        defer {
           objc_sync_exit(array)
        }
        // manipulate the array
        

        第二个

        func sync(lock: NSObject, closure: () -> Void) {
            objc_sync_enter(lock)
            defer {
                objc_sync_exit(lock)
            }
            closure()
        }
        

        【讨论】:

        【解决方案9】:

        首先,objc_sync_enter 不起作用

        objc_sync_enter(array)
        defer {
           objc_sync_exit(array)
        }
        

        原因objc_sync_enter / objc_sync_exit not working with DISPATCH_QUEUE_PRIORITY_LOW

        objc_sync_enter 是一个非常低级的原语,不打算直接使用。这是 ObjC 中旧的 @synchronized 系统的实现细节。

        对于 swift,应该像 @Kirsteins 所说的那样使用,我建议使用同步而不是异步:

        private let syncQueue = DispatchQueue(label:"com.test.LockQueue") 
        func test(){
            self.syncQueue.sync{
                // thread safe code here
            }
        }
        

        【讨论】:

        • 带同步的串行队列不会导致死锁?
        • 感谢@karthik,在某些情况下同步确实导致了死锁。但是对于我的项目,使用它的队列没有资源依赖,所以没问题。
        【解决方案10】:

        详情

        • Xcode 10.1 (10B61)、Swift 4.2
        • Xcode 10.2.1 (10E1001)、Swift 5

        解决方案

        import Foundation
        
        // https://developer.apple.com/documentation/swift/rangereplaceablecollection
        struct AtomicArray<T>: RangeReplaceableCollection {
        
            typealias Element = T
            typealias Index = Int
            typealias SubSequence = AtomicArray<T>
            typealias Indices = Range<Int>
            fileprivate var array: Array<T>
            var startIndex: Int { return array.startIndex }
            var endIndex: Int { return array.endIndex }
            var indices: Range<Int> { return array.indices }
        
            func index(after i: Int) -> Int { return array.index(after: i) }
        
            private var semaphore = DispatchSemaphore(value: 1)
            fileprivate func _wait() { semaphore.wait() }
            fileprivate func _signal() { semaphore.signal() }
        }
        
        // MARK: - Instance Methods
        
        extension AtomicArray {
        
            init<S>(_ elements: S) where S : Sequence, AtomicArray.Element == S.Element {
                array = Array<S.Element>(elements)
            }
        
            init() { self.init([]) }
        
            init(repeating repeatedValue: AtomicArray.Element, count: Int) {
                let array = Array(repeating: repeatedValue, count: count)
                self.init(array)
            }
        }
        
        // MARK: - Instance Methods
        
        extension AtomicArray {
        
            public mutating func append(_ newElement: AtomicArray.Element) {
                _wait(); defer { _signal() }
                array.append(newElement)
            }
        
            public mutating func append<S>(contentsOf newElements: S) where S : Sequence, AtomicArray.Element == S.Element {
                _wait(); defer { _signal() }
                array.append(contentsOf: newElements)
            }
        
            func filter(_ isIncluded: (AtomicArray.Element) throws -> Bool) rethrows -> AtomicArray {
                _wait(); defer { _signal() }
                let subArray = try array.filter(isIncluded)
                return AtomicArray(subArray)
            }
        
            public mutating func insert(_ newElement: AtomicArray.Element, at i: AtomicArray.Index) {
                _wait(); defer { _signal() }
                array.insert(newElement, at: i)
            }
        
            mutating func insert<S>(contentsOf newElements: S, at i: AtomicArray.Index) where S : Collection, AtomicArray.Element == S.Element {
                _wait(); defer { _signal() }
                array.insert(contentsOf: newElements, at: i)
            }
        
            mutating func popLast() -> AtomicArray.Element? {
                _wait(); defer { _signal() }
                return array.popLast()
            }
        
            @discardableResult mutating func remove(at i: AtomicArray.Index) -> AtomicArray.Element {
                _wait(); defer { _signal() }
                return array.remove(at: i)
            }
        
            mutating func removeAll() {
                _wait(); defer { _signal() }
                array.removeAll()
            }
        
            mutating func removeAll(keepingCapacity keepCapacity: Bool) {
                _wait(); defer { _signal() }
                array.removeAll()
            }
        
            mutating func removeAll(where shouldBeRemoved: (AtomicArray.Element) throws -> Bool) rethrows {
                _wait(); defer { _signal() }
                try array.removeAll(where: shouldBeRemoved)
            }
        
            @discardableResult mutating func removeFirst() -> AtomicArray.Element {
                _wait(); defer { _signal() }
                return array.removeFirst()
            }
        
            mutating func removeFirst(_ k: Int) {
                _wait(); defer { _signal() }
                array.removeFirst(k)
            }
        
            @discardableResult mutating func removeLast() -> AtomicArray.Element {
                _wait(); defer { _signal() }
                return array.removeLast()
            }
        
            mutating func removeLast(_ k: Int) {
                _wait(); defer { _signal() }
                array.removeLast(k)
            }
        
            @inlinable public func forEach(_ body: (Element) throws -> Void) rethrows {
                _wait(); defer { _signal() }
                try array.forEach(body)
            }
        
            mutating func removeFirstIfExist(where shouldBeRemoved: (AtomicArray.Element) throws -> Bool) {
                _wait(); defer { _signal() }
                guard let index = try? array.firstIndex(where: shouldBeRemoved) else { return }
                array.remove(at: index)
            }
        
            mutating func removeSubrange(_ bounds: Range<Int>) {
                _wait(); defer { _signal() }
                array.removeSubrange(bounds)
            }
        
            mutating func replaceSubrange<C, R>(_ subrange: R, with newElements: C) where C : Collection, R : RangeExpression, T == C.Element, AtomicArray<Element>.Index == R.Bound {
                _wait(); defer { _signal() }
                array.replaceSubrange(subrange, with: newElements)
            }
        
            mutating func reserveCapacity(_ n: Int) {
                _wait(); defer { _signal() }
                array.reserveCapacity(n)
            }
        
            public var count: Int {
                _wait(); defer { _signal() }
                return array.count
            }
        
            public var isEmpty: Bool {
                _wait(); defer { _signal() }
                return array.isEmpty
            }
        }
        
        // MARK: - Get/Set
        
        extension AtomicArray {
        
            // Single  action
        
            func get() -> [T] {
                _wait(); defer { _signal() }
                return array
            }
        
            mutating func set(array: [T]) {
                _wait(); defer { _signal() }
                self.array = array
            }
        
            // Multy actions
        
            mutating func get(closure: ([T])->()) {
                _wait(); defer { _signal() }
                closure(array)
            }
        
            mutating func set(closure: ([T]) -> ([T])) {
                _wait(); defer { _signal() }
                array = closure(array)
            }
        }
        
        // MARK: - Subscripts
        
        extension AtomicArray {
        
            subscript(bounds: Range<AtomicArray.Index>) -> AtomicArray.SubSequence {
                get {
                    _wait(); defer { _signal() }
                    return AtomicArray(array[bounds])
                }
            }
        
            subscript(bounds: AtomicArray.Index) -> AtomicArray.Element {
                get {
                    _wait(); defer { _signal() }
                    return array[bounds]
                }
                set(value) {
                    _wait(); defer { _signal() }
                    array[bounds] = value
                }
            }
        }
        
        // MARK: - Operator Functions
        
        extension AtomicArray {
        
            static func + <Other>(lhs: Other, rhs: AtomicArray) -> AtomicArray where Other : Sequence, AtomicArray.Element == Other.Element {
                return AtomicArray(lhs + rhs.get())
            }
        
            static func + <Other>(lhs: AtomicArray, rhs: Other) -> AtomicArray where Other : Sequence, AtomicArray.Element == Other.Element {
                return AtomicArray(lhs.get() + rhs)
            }
        
            static func + <Other>(lhs: AtomicArray, rhs: Other) -> AtomicArray where Other : RangeReplaceableCollection, AtomicArray.Element == Other.Element {
                return AtomicArray(lhs.get() + rhs)
            }
        
            static func + (lhs: AtomicArray<Element>, rhs: AtomicArray<Element>) -> AtomicArray {
                return AtomicArray(lhs.get() + rhs.get())
            }
        
            static func += <Other>(lhs: inout AtomicArray, rhs: Other) where Other : Sequence, AtomicArray.Element == Other.Element {
                lhs._wait(); defer { lhs._signal() }
                lhs.array += rhs
            }
        }
        
        // MARK: - CustomStringConvertible
        
        extension AtomicArray: CustomStringConvertible {
            var description: String {
                _wait(); defer { _signal() }
                return "\(array)"
            }
        }
        
        // MARK: - Equatable
        
        extension AtomicArray where Element : Equatable {
        
            func split(separator: Element, maxSplits: Int, omittingEmptySubsequences: Bool) -> [ArraySlice<Element>] {
                _wait(); defer { _signal() }
                return array.split(separator: separator, maxSplits: maxSplits, omittingEmptySubsequences: omittingEmptySubsequences)
            }
        
            func firstIndex(of element: Element) -> Int? {
                _wait(); defer { _signal() }
                return array.firstIndex(of: element)
            }
        
            func lastIndex(of element: Element) -> Int? {
                _wait(); defer { _signal() }
                return array.lastIndex(of: element)
            }
        
            func starts<PossiblePrefix>(with possiblePrefix: PossiblePrefix) -> Bool where PossiblePrefix : Sequence, Element == PossiblePrefix.Element {
                _wait(); defer { _signal() }
                return array.starts(with: possiblePrefix)
            }
        
            func elementsEqual<OtherSequence>(_ other: OtherSequence) -> Bool where OtherSequence : Sequence, Element == OtherSequence.Element {
                _wait(); defer { _signal() }
                return array.elementsEqual(other)
            }
        
            func contains(_ element: Element) -> Bool {
                _wait(); defer { _signal() }
                return array.contains(element)
            }
        
            static func != (lhs: AtomicArray<Element>, rhs: AtomicArray<Element>) -> Bool {
                lhs._wait(); defer { lhs._signal() }
                rhs._wait(); defer { rhs._signal() }
                return lhs.array != rhs.array
            }
        
            static func == (lhs: AtomicArray<Element>, rhs: AtomicArray<Element>) -> Bool {
                lhs._wait(); defer { lhs._signal() }
                rhs._wait(); defer { rhs._signal() }
                return lhs.array == rhs.array
            }
        }
        

        使用示例 1

        import Foundation
        
        // init
        var array = AtomicArray<Int>()
        print(array)
        array = AtomicArray(repeating: 0, count: 5)
        print(array)
        array = AtomicArray([1,2,3,4,5,6,7,8,9])
        print(array)
        
        // add
        array.append(0)
        print(array)
        array.append(contentsOf: [5,5,5])
        print(array)
        
        // filter
        array = array.filter { $0 < 7 }
        print(array)
        
        // map
        let strings = array.map { "\($0)" }
        print(strings)
        
        // insert
        array.insert(99, at: 5)
        print(array)
        array.insert(contentsOf: [2, 2, 2], at: 0)
        print(array)
        
        // pop
        _ = array.popLast()
        print(array)
        _ = array.popFirst()
        print(array)
        
        // remove
        array.removeFirst()
        print(array)
        array.removeFirst(3)
        print(array)
        array.remove(at: 2)
        print(array)
        array.removeLast()
        print(array)
        array.removeLast(5)
        print(array)
        array.removeAll { $0%2 == 0 }
        print(array)
        array = AtomicArray([1,2,3,4,5,6,7,8,9,0])
        array.removeSubrange(0...2)
        print(array)
        array.replaceSubrange(0...2, with: [0,0,0])
        print(array)
        array.removeAll()
        print(array)
        
        array.set(array: [1,2,3,4,5,6,7,8,9,0])
        print(array)
        
        // subscript
        print(array[0])
        array[0] = 100
        print(array)
        print(array[1...4])
        
        // operator functions
        array = [1,2,3] + AtomicArray([4,5,6])
        print(array)
        array = AtomicArray([4,5,6]) + [1,2,3]
        print(array)
        array = AtomicArray([1,2,3]) + AtomicArray([4,5,6])
        print(array)
        

        使用示例 2

        import Foundation
        
        var arr = AtomicArray([0,1,2,3,4,5])
        for i in 0...1000 {
            // Single actions
            DispatchQueue.global(qos: .background).async {
                usleep(useconds_t(Int.random(in: 100...10000)))
                let num = i*i
                arr.append(num)
                print("arr.append(\(num)), background queue")
            }
            DispatchQueue.global(qos: .default).async {
                usleep(useconds_t(Int.random(in: 100...10000)))
                arr.append(arr.count)
                print("arr.append(\(arr.count)), default queue")
            }
        
            // multy actions
            DispatchQueue.global(qos: .utility).async {
                arr.set { array -> [Int] in
                    var newArray = array
                    newArray.sort()
                    print("sort(), .utility queue")
                    return newArray
                }
            }
        }
        

        【讨论】:

          【解决方案11】:

          这是 Swift 4 的答案,

          let queue = DispatchQueue(label: "com.readerWriter", qos: .background, attributes: .concurrent)
          var safeArray: [String] = []
          
          subscript(index: Int) -> String {
          
              get {
                  queue.sync {
                      return safeArray[index]
                  }
              }
          
              set(newValue) {
                  queue.async(flags: .barrier) { [weak self] in
                      self?.safeArray[index] = newValue
                  }
              }
          }
          

          【讨论】:

          • 不要使用队列进行锁定。它只是在内部使用锁并分派到另一个线程,这是完全没有必要的。使用锁。
          【解决方案12】:

          Swift-Nio 和 Vapor Swift

          对于那些使用 Swift-Nio(或基于 Swift-Nio 的 Vapor Swift)的人来说,有一个内置的解决方案可以解决这个问题:

          class MyClass {
              let lock = Lock()
              var myArray: Array<Int> = []
          
              func networkRequestWhatEver() {
                  lock.withLock {
                      array.append(someValue)
                  }
              }
          }
          

          请注意,在修改相同的Array 对象(或Dictionary 等)时,您应该使用相同的Lock 对象。

          https://github.com/apple/swift-nio/blob/5e728b57862ce9e13877ff1edc9249adc933070a/Sources/NIOConcurrencyHelpers/lock.swift#L15

          【讨论】:

          • import NIOConcurrencyHelpers
          • 为什么要导入整个库只是为了锁定?
          • 如果一个人已经在使用 vapor swift 和 swift-nio,这可能是最简单的解决方案。
          【解决方案13】:

          我不知道为什么人们对这么简单的事情采取如此复杂的方法

          • 不要滥用DispatchQueues 进行锁定。使用queue.sync 只不过是在锁(DispatchGroup)等待时获取锁并将工作分派给另一个线程。这不仅是不必要的,而且还可能会产生副作用,具体取决于您锁定的内容。您可以在GCD Source中自行查找

          • 不要使用objc_sync_enter/exit,它们被ObjCs @synchronized 使用,它将隐式地将Swift 集合连接到ObjC 对应物,这也是不必要的。它是一个遗留 API。

          只需定义一个锁,并保护您的收藏访问。

          var lock = DispatchSemaphore(value: 1)
          var a = [1, 2, 3]
          
          lock.wait()
          a.append(4)
          lock.signal()
          

          如果你想让你的生活更轻松一点,定义一个小的扩展。

          extension DispatchSemaphore {
          
              @discardableResult
              func with<T>(_ block: () throws -> T) rethrows -> T {
                  wait()
                  defer { signal() }
                  return try block()
              }
          }
          
          let lock = DispatchSemaphore(value: 1)
          var a = [1, 2, 3]
          
          lock.with { a.append(4) }
          

          您还可以定义 @propertyWrapper 以使您的成员 vars 原子。

          @propertyWrapper
          struct Atomic<Value> {
          
              private let lock = DispatchSemaphore(value: 1)
              private var value: Value
          
              init(default: Value) {
                  self.value = `default`
              }
          
              var wrappedValue: Value {
                  get {
                      lock.wait()
                      defer { lock.signal() }
                      return value
                  }
                  set {
                      lock.wait()
                      value = newValue
                      lock.signal()
                  }
              }
          }
          

          最后但并非最不重要的是,对于原始原子类型,还有 Swift Atomics

          【讨论】:

          • 似乎在 willSet 属性观察器中添加对 .lock() 的调用(以及在 didSet 中相应的 .unlock() )是可行的。这意味着您不必在呼叫站点进行任何其他代码更改。 (如果您删除对 .unlock() 的调用,您可以确认这确实有效)。我想你也可以为此创建一个属性包装器。
          • 是的,你也可以制作一个@propertyWrapper
          【解决方案14】:

          带有 Actor 的线程安全数据结构

          从 Swift 5.5 开始,您可以使用演员来表达这一点:

          actor SyncArray<T> {
              private var buffer: [T]
              
              init<S: Sequence>(_ elements: S) where S.Element == T {
                  buffer = Array(elements)
              }
              
              var count: Int {
                  buffer.count
              }
              
              func append(_ element: T) {
                  buffer.append(element)
              }
              
              @discardableResult
              func remove(at index: Int) -> T {
                  buffer.remove(at: index)
              }
          }
          

          它不仅使代码更简单,更不容易出错,而且更明确地指出了另一个答案中指出的潜在竞争条件:

          Task {
              let array = SyncArray([1])
          
              if await array.count == 1 { 
                  await array.remove(at: 0)
              }
          }
          

          这里有两个暂停点,这意味着在调用.remove(at:) 时,数组count 可能已经改变。

          这样的read-then-write操作必须是原子的才能保持一致,因此应该将其定义为actor上的方法:

          extension SyncArray {
              func removeLastIfSizeOfOne() {
                  if buffer.count == 1 {
                      buffer.remove(at: 0)
                  }
              }
          }
          

          这里,没有挂起点表示操作是原子执行的。另一种无需编写扩展即可工作的解决方案是使用 isolated 关键字,如下所示:

          func removeLastIfSizeOfOne<T>(_ array: isolated SyncArray<T>) {
              if array == 1 {
                  array(at: 0)
              }
          }
          

          这将在整个调用期间隔离传递的参与者,而不是在其每个暂停点。调用这个函数只需要一个暂停点。

          【讨论】:

            【解决方案15】:

            Swift 线程安全集合

            在 Swift 中使某些东西(例如 Collection)线程安全的主要和最常见的想法是:

            1. 自定义(本地)并发队列
            2. 同步阅读。通过sync 读取临界区(共享资源)
            3. 异步写入障碍

            [Swift Thread safe singleton]

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-07-08
              • 1970-01-01
              • 2021-06-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多