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.
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 struct
s 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
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])
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
}
}
}
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...
}
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
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
}
}
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;
});
}
Utilisez DispatchQueue
pour synchroniser
http://basememara.com/creating-thread-safe-arrays-in-Swift/
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)
}
}
}
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)"
}
}
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)
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
}
}
}
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()
}