web-dev-qa-db-fra.com

Créez un tableau de threads sécurisés dans swift

J'ai un problème de filetage dans Swift. J'ai un tableau avec des objets. Sur un délégué, la classe récupère de nouveaux objets toutes les secondes. Après cela, je dois vérifier si les objets sont déjà dans le tableau, je dois donc mettre à jour l'objet, sinon je dois supprimer/ajouter le nouvel objet.

Si j'ajoute un nouvel objet, je dois d'abord récupérer des données sur le réseau. Ceci est handelt via un bloc.

Maintenant, comment puis-je synchroniser ces tâches?

J'ai essayé un dispatch_semaphore, mais celui-ci bloque l'interface utilisateur jusqu'à la fin du blocage.

J'ai également essayé une variable bool simple, qui vérifie si le bloc est actuellement exécuté et ignore la méthode de comparaison.

Mais les deux méthodes ne sont pas idéales.

Quelle est la meilleure façon de gérer le tableau, je ne veux pas avoir des données en double dans le tableau.

46
patrickS

Kirsteins est correct, mais vous n'avez pas toujours besoin d'utiliser la file d'attente de répartition. Vous pouvez utiliser:

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

Cela devrait faire l'affaire. Pour un bonus supplémentaire, vous pouvez créer une fonction à utiliser chaque fois que vous avez besoin de la sécurité des threads:

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

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

Notez que j'ai changé AnyObject en NSObject. Dans Swift, les types de collection sont implémentés sous la forme structs et ils sont passés par valeur, donc je suppose qu'il serait plus sûr de travailler avec mutable des classes de collection qui sont passé par référence lorsque vous utilisez la fonction pratique sync.

Mise à jour pour Swift

Le modèle recommandé pour l'accès thread-safe utilise 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

Mon approche de ce problème consistait à utiliser une file d'attente de répartition en série afin de synchroniser l'accès au tableau en boîte. Il va bloquer le thread lorsque vous essayez d'obtenir la valeur à index et que la file d'attente est vraiment occupée, mais c'est également le problème des verrous.

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

La réponse de Kirsteins est correcte, mais pour des raisons pratiques, j'ai mis à jour cette réponse avec les suggestions d'Amol Chaudhari et Rob sur l'utilisation d'une file d'attente simultanée avec barrière asynchrone pour autoriser les lectures simultanées mais le blocage des écritures.

J'ai également intégré d'autres fonctions de tableau qui m'ont été utiles.

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 Il s'agit du même code, mis à jour pour 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
    }
}
}
28
rmooney

Un détail mineur: dans Swift 3 (au moins dans XCode 8 Beta 6), la syntaxe des files d'attente a été modifiée de manière significative. Les changements importants apportés à la réponse de @Kirsteins seront les suivants:

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

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

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

Je pense que dispatch_barriers vaut la peine d'être examiné. Utiliser gcd pour la synchronicité m'est plus intuitif que d'utiliser le mot-clé synchronize pour éviter la mutation de l'état à partir de plusieurs threads.

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

2
amol-c

tout d'abord, objc_sync_enter ne fonctionne pas

objc_sync_enter(array)
defer {
   objc_sync_exit(array)
}

raison objc_sync_enter/objc_sync_exit ne fonctionne pas avec DISPATCH_QUEUE_PRIORITY_LOW

objc_sync_enter est une primitive de bas niveau, et n'est pas destinée à être utilisée directement. C'est un détail d'implémentation de l'ancien système @synchronized dans ObjC.

pour Swift, devrait utiliser comme ceci, comme @Kirsteins l'a dit, et je suggère de synchroniser au lieu d'async:

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

Il existe une excellente réponse ici qui est threadsafe et ne bloque pas les lectures simultanées: https://stackoverflow.com/a/15936959/2050665

C'est écrit en Objective C, mais le portage sur Swift est 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;
  });
}

Crédit à https://stackoverflow.com/users/97337/rob-napier

1
River Satya

Approche:

Utilisez DispatchQueue pour synchroniser

Référer:

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

Code:

Vous trouverez ci-dessous une implémentation grossière d'un tableau thread-safe que vous pouvez ajuster.

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

Détails

  • Xcode 10.1 (10B61)
  • Swift 4.2

Solution

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

Exemple d'utilisation 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)

Exemple d'utilisation 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
Vasily Bodnarchuk

Pour améliorer la réponse acceptée Je suggérerais d'utiliser différer:

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

et le second

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