web-dev-qa-db-de.com

Erstellen Sie in kürzester Zeit ein Thread-sicheres Array

Ich habe ein Problem mit dem Einfädeln in Swift. Ich habe ein Array mit einigen Objekten darin. Über einen Delegierten erhält die Klasse jede Sekunde neue Objekte. Danach muss ich überprüfen, ob sich die Objekte bereits im Array befinden, also muss ich das Objekt aktualisieren, ansonsten muss ich das neue Objekt löschen/hinzufügen.

Wenn ich ein neues Objekt hinzufüge, muss ich zuerst einige Daten über das Netzwerk abrufen. Dies erfolgt über einen Block.

Jetzt ist mein Problem, wie kann ich diese Aufgaben synchronisieren?

Ich habe ein dispatch_semaphore ausprobiert, aber dieses blockiert die Benutzeroberfläche, bis der Block abgeschlossen ist.

Ich habe auch eine einfache bool-Variable ausprobiert, die überprüft, ob der Block gerade ausgeführt wird, und die Vergleichsmethode zwischenzeitlich überspringt.

Beide Methoden sind jedoch nicht ideal.

Was ist der beste Weg, um das Array zu verwalten? Ich möchte keine doppelten Daten im Array haben.

46
patrickS

Kirsteins ist korrekt, aber Sie müssen nicht immer eine Versandwarteschlange verwenden. Sie können verwenden:

objc_sync_enter(array)
// manipulate the array
objc_sync_exit(array)

Das sollte den Trick tun. Für zusätzlichen Bonus können Sie eine Funktion erstellen, die Sie verwenden können, wenn Sie Threadsicherheit benötigen:

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

...
var list = NSMutableArray()
sync (list) {
   list.addObject("something")
}

Beachten Sie, dass ich AnyObject in NSObject geändert habe. In Swift-Auflistungstypen werden structs implementiert. Sie werden von value übergeben. Ich denke, es wäre sicherer, mit mutable - Auflistungsklassen zu arbeiten, die durch Referenz übergeben werden bei Verwendung der praktischen sync-Funktion.

Update für Swift

Das empfohlene Muster für den Thread-sicheren Zugriff ist die Verwendung von 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
62
skim

Mein Ansatz für dieses Problem bestand in der Verwendung der seriellen Versandwarteschlange, um den Zugriff auf das Boxed Array zu synchronisieren. Es wird den Thread blockieren, wenn Sie versuchen, den Wert am Index abzurufen, und die Warteschlange ist sehr beschäftigt.

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])
30
Kirsteins

Kirsteins 'Antwort ist korrekt, aber aus Gründen der Bequemlichkeit habe ich diese Antwort mit den Vorschlägen von Amol Chaudhari und Rob überarbeitet, eine gleichzeitige Warteschlange mit asynchroner Barriere zu verwenden, um gleichzeitiges Lesen zu ermöglichen, aber Schreibvorgänge zu blockieren.

Ich habe auch einige andere Array-Funktionen geschrieben, die für mich nützlich waren.

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
    }
}
}

UPDATE Dies ist derselbe Code, der für Swift3 aktualisiert wurde.

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
    }
}
}
28
rmooney

Ein kleines Detail: In Swift 3 (zumindest in XCode 8 Beta 6) hat sich die Syntax für Warteschlangen erheblich geändert. Die wichtigsten Änderungen an @Kirsteins 'Antwort sind:

private let accessQueue = DispatchQueue(label: "SynchronizedArrayAccess")

txAccessQueue.async() {
  // Your async code goes here...
}

txAccessQueue.sync() {
  // Your sync code goes here...
}
7
nbloqs

Ich denke, dispatch_barriers sind einen Blick wert. Die Verwendung von gcd für die Synchronität ist für mich intuitiver als die Verwendung des Schlüsselworts "synchronisieren", um Statusmutationen von mehreren Threads zu vermeiden.

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

2
amol-c

erstens funktioniert objc_sync_enter nicht

objc_sync_enter(array)
defer {
   objc_sync_exit(array)
}

grund objc_sync_enter/objc_sync_exit funktioniert nicht mit DISPATCH_QUEUE_PRIORITY_LOW

objc_sync_enter ist ein extrem einfaches Primitiv und ist nicht für die direkte Verwendung gedacht. Es handelt sich um ein Implementierungsdetail des alten @ synchronisierten Systems in ObjC.

für Swift sollte dies genauso verwendet werden, wie @Kirsteins gesagt hat, und ich empfehle Sync anstelle von Async:

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

Hier gibt es eine großartige Antwort, die threadsicher ist und gleichzeitige Lesevorgänge nicht blockiert: https://stackoverflow.com/a/15936959/2050665

Es ist in Objective C geschrieben, aber die Portierung auf Swift ist trivial.

@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;
  });
}

Gutschrift an https://stackoverflow.com/users/97337/rob-napier

1
River Satya

Ansatz:

Verwenden Sie DispatchQueue zum Synchronisieren

Verweisen:

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

Code:

Nachfolgend finden Sie eine grobe Implementierung eines Thread-sicheren Arrays. Sie können es fein abstimmen.

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)
        }
    }
}
1
user1046037

Einzelheiten

  • Xcode 10,1 (10B61)
  • Schnell 4.2

Lösung

import Foundation

// https://developer.Apple.com/documentation/Swift/rangereplaceablecollection

struct AtomicArray<T: Equatable>: RangeReplaceableCollection {

    typealias Element = T
    typealias Index = Int
    typealias SubSequence = AtomicArray<T>
    typealias Indices = Range<Int>
    fileprivate var array: Array<T>
    private var arraySemaphore: DispatchSemaphore

    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) }

    fileprivate func _wait() { arraySemaphore.wait() }
    fileprivate func _signal() { arraySemaphore.signal() }
}

// Instance Methods

extension AtomicArray {

    init<S>(_ elements: S) where S : Sequence, AtomicArray.Element == S.Element {
        array = Array<S.Element>(elements)
        arraySemaphore = DispatchSemaphore(value: 1)
    }

    init() { self.init([]) }

    init(repeating repeatedValue: AtomicArray.Element, count: Int) {
        let array = Array(repeating: repeatedValue, count: count)
        self.init(array)
    }
}

// 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(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)
    }

    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<T>.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
    }
}

// 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)
    }
}

// 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
        }
    }
}

// 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<T>, rhs: AtomicArray<T>) -> 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
    }
}

extension AtomicArray: Equatable {
    static func == (lhs: AtomicArray<T>, rhs: AtomicArray<T>) -> Bool {
        lhs._wait(); defer { lhs._signal() }
        rhs._wait(); defer { rhs._signal() }
        return lhs.array == rhs.array
    }
}

extension AtomicArray: CustomStringConvertible {
    var description: String {
        _wait(); defer { _signal() }
        return "\(array)"
    }
}

Verwendungsbeispiel 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)

Verwendungsbeispiel 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
        }
    }
}
1

Um die akzeptierte Antwort zu verbessern Ich würde die Verwendung von verzögern vorschlagen:

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

und der zweite

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