Создать потокобезопасный массив в Swift

У меня проблема с потоками в Swift. У меня есть массив с некоторыми объектами. Через делегат класс получает новые объекты примерно каждую секунду. После этого я должен проверить, находятся ли объекты уже в массиве, поэтому мне нужно обновить объект, в противном случае мне придется удалить / добавить новый объект.

Если я добавляю новый объект, мне нужно сначала получить некоторые данные по сети. Это осуществляется через блок.

Теперь моя проблема в том, как мне синхронизировать эти задачи?

Я пробовал использовать dispatch_semaphore, но он блокирует пользовательский интерфейс, пока блок не будет завершен.

Я также пробовал простую переменную типа bool, которая проверяет, выполняется ли блок в данный момент, и пропускает метод сравнения.

Но оба метода не идеальны.

Как лучше всего управлять массивом? Я не хочу, чтобы в массиве были повторяющиеся данные.


person patrickS    schedule 28.01.2015    source источник
comment
Если вам не нужны повторяющиеся данные в массиве, используйте Set   -  person Artem Zaytsev    schedule 23.04.2021


Ответы (13)


Обновление для Swift

Рекомендуемый шаблон для поточно-безопасного доступа - использование диспетчеризации 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
person skim    schedule 11.03.2015
comment
Спасибо, но каковы преимущества / недостатки objc_sync_enter vs dispatch_queue? - person Crashalot; 02.04.2016
comment
objc_sync_enter - это то, что находится под @synchronized Objective-C. Поскольку я на самом деле не смотрел на то, что находится под objc_sync_enter, я вроде как верю, что открытый исходный код работает так, как обещал. Что касается использования последовательной очереди, вы точно знаете, как это работает (блоки кодов последовательно). Я предполагаю, что обратная сторона создания и управления последовательной очередью для каждого экземпляра поточно-безопасного типа коллекции может быть дорогостоящей. - person skim; 04.04.2016
comment
Мне стало любопытно, поэтому я нашел это на objc_sync_enter: rykap.com/objective -c / 2015/05/09 / синхронизировано - person skim; 04.04.2016
comment
это идеально подходит для кортежей и NON AnyObject, если у вас есть класс Wrapper, как здесь stackoverflow.com/a/28140922/1264893 - person iluvatar_GR; 24.07.2016
comment
Небезопасно / правильно использовать objc_sync_enter для типа значения, такого как Array. objc_sync_array выполняет сравнение указателей, и не гарантируется, что время жизни указателя мостового типа значения Swift будет стабильным (и фактически большую часть времени оно нестабильно). - person Jack Lawrence; 31.08.2016
comment
@skim Интересно, сможем ли мы это сделать? Это сэкономит немного хассела? `` `имя переменной: Строка? {set {objc_sync_enter (innerName) innerName = newValue objc_sync_exit (innerName)} получить {objc_sync_enter (innerName) let newValue = innerName objc_sync_exit (innerName) return newValue}} private (set) var innerName? `` '' - person allenlinli; 19.12.2016
comment
это не работает, причина stackoverflow.com/questions/35084754/ - person lbsweek; 03.05.2018

Ответ Кирстейнса правильный, но для удобства я обновил этот ответ, добавив в него предложения Амола Чаудхари и Роба по использованию параллельной очереди с асинхронным барьером, чтобы разрешить одновременное чтение, но блокировать запись.

Я также обернул некоторые другие функции массива, которые были мне полезны.

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
    }
}
}
person rmooney    schedule 14.10.2016
comment
removeAtIndex может удалить неправильный элемент в этом коде, потому что idx может быть изменен другим потоком removeAtIndex ... - person Maxim Kholyavkin; 20.11.2016
comment
@ Speakus мог ошибаться, но я почти уверен, что это не так. Это асинхронно, но в той же параллельной очереди, что и другие операции с массивом ... поэтому removeAtIndex произойдет до того, как произойдет какая-либо другая операция чтения или записи. - person Jordan Smith; 24.01.2017
comment
@JordanSmith Проблема произойдет, когда вы вызовете removeAtIndex, но код переключится на другой поток, например, когда вы получите self.accessQueue, но до запуска .async. - person Maxim Kholyavkin; 24.01.2017
comment
Не могли бы вы объяснить, как использовать этот класс SynchronizedArray вместо обычного массива swift 3 ([String] или [])? Это очень срочно - person Varun Naharia; 15.03.2017
comment
Используйте его как массив в классе, к которому можно получить доступ из нескольких потоков. let array = SynchronizedArray ‹String› () array.append (Новая строка) print (array [0]) array.removeAtIndex (index: 0) print (array.count) - person rmooney; 16.03.2017
comment
Это должен быть правильный ответ. _1 _ / _ 2_ не даст вам одновременных чтений, а ответ @rmooneys - чистый Swift - person fruitcoder; 15.06.2017
comment
@mooney Я думаю, что @Speakus прямо здесь: скажем, в синхронизированном массиве есть один элемент, разве это не сработает? if synchronizedArray.count == 1 { synchronizedArray.remove(at: 0) } Это состояние гонки, скажем, два потока выполняют инструкцию. Оба одновременно читают счетчик, равный 1, и одновременно ставят блок записи в очередь. Блоки записи выполняются последовательно, второй выйдет из строя. Я ошибся? Это ошибка дизайнера или пользователя? В любом случае это чертовски хрупко, и мне никогда не захочется касаться многопоточности ... Как бы вы вообще сделали это безопасным? Вернуться к эксклюзивным блокировкам как для чтения, так и для записи? - person tombardey; 29.06.2017
comment
@tombardey - Вы абсолютно правы в том, что этого уровня синхронизации (на уровне свойств / методов) часто недостаточно для достижения истинной безопасности потоков в более широких приложениях. Ваш пример легко решается (путем добавления метода, который отправляет блок в очередь), но есть и другие, которых нет (например, синхронизированный массив, одновременно используемый UITableViewDataSource и измененный какой-либо фоновой операцией). В таких случаях вам необходимо реализовать собственную синхронизацию более высокого уровня. Но, тем не менее, описанный выше метод очень полезен в определенных, очень ограниченных ситуациях. - person Rob; 24.12.2017
comment
@rmooney - В примерах Swift 3 и 4 вы можете упростить некоторые из этих подпрограмм, исключив неявно развернутые опции, например var first: T? { return accessQueue.sync { self.array.first } } - person Rob; 24.12.2017

Мой подход к этой проблеме заключался в использовании очереди последовательной отправки для синхронизации доступа к массиву в штучной упаковке. Он заблокирует поток, когда вы попытаетесь получить значение по индексу, а очередь действительно занята, но это проблема и с блокировками.

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])
person Kirsteins    schedule 28.01.2015
comment
Вы можете рассмотреть возможность использования шаблона «читатель-писатель»: используйте DISPATCH_QUEUE_CONCURRENT; изменение записи с dispatch_async на dispatch_barrier_async; но при выходе читается как dispatch_sync. Это дает вам одновременное чтение, но записи по-прежнему синхронизируются. - person Rob; 08.04.2015
comment
Вы делаете dispatch_barrier_async только для записи. Но делайте чтение с dispatch_sync. Таким образом, хотя записи не будут (и не должны) происходить одновременно по отношению к чему-либо еще, чтение может происходить одновременно с другими чтениями. См. Шаблон № 6 в последней части видеоролика WWDC 2012 Асинхронные шаблоны проектирования с блоками, GCD , и XPC. - person Rob; 05.11.2015
comment
Как бы вы прошли сортировку для нестандартных типов? - person Fred Faust; 24.01.2016
comment
Разве DispatchQueue не требует, чтобы ЦП переключал контекст на другой поток, что приводит к очень дорогостоящей операции? Или это sync(), который на самом деле решает эту проблему, не отправляя в очередь, если это не необходимо, потому что это доступ к ресурсам только для чтения? - person Lars Blumberg; 11.01.2017

Я не знаю, почему люди применяют такие сложные подходы к такой простой вещи

  • Не злоупотребляйте DispatchQueues для блокировки. Использование queue.sync - это не что иное, как получение блокировки и отправка работы другому потоку, пока блокировка (DispatchGroup) ожидает. Это не только не нужно, но и может иметь побочные эффекты в зависимости от того, что вы блокируете. Вы можете найти это сами в исходном коде GCD

  • Не используйте 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()
        }
    }
}
person Erik Aigner    schedule 28.06.2020
comment
Кажется, что добавление вызова .lock () в обозревателе свойств willSet (и соответствующий .unlock () в didSet) работает. Это означает, что вам не нужно вносить какие-либо другие изменения кода на сайтах обзвона. (Если вы удалите вызов .unlock (), вы можете подтвердить, что это работает). Я полагаю, вы, вероятно, могли бы создать для этого тоже обертку свойств. - person rennarda; 25.09.2020
comment
Да, вы тоже можете создать @propertyWrapper - person Erik Aigner; 25.09.2020

Подробности

  • 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
        }
    }
}
person Vasily Bodnarchuk    schedule 07.02.2019

Небольшая деталь: в 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...
}
person nbloqs    schedule 30.08.2016

Свифт-Нио и Вапор Свифт

Для тех из вас, кто использует Swift-Nio (или Vapor Swift, основанный на Swift-Nio), есть встроенное решение этой проблемы:

class MyClass {
    let lock = Lock()
    var myArray: Array<Int> = []

    func networkRequestWhatEver() {
        lock.withLock {
            array.append(someValue)
        }
    }
}

Обратите внимание, что вы должны использовать один и тот же объект Lock при изменении одного и того же объекта Array (или Dictionary и т. Д.).

person swift-lynx    schedule 26.03.2020

comment
Зачем вам импортировать целую библиотеку только для блокировки? - person Erik Aigner; 28.06.2020
comment
Если кто-то в любом случае уже использует steam swift и swift-nio, это, вероятно, самое простое решение. - person swift-lynx; 01.07.2020

Вот ответ для 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
        }
    }
}
person Sathish Kumar Gurunathan    schedule 14.01.2020
comment
Не используйте очереди для блокировки. Он просто использует внутреннюю блокировку и отправляет в другой поток, что совершенно не нужно. Используйте замок. - person Erik Aigner; 28.06.2020

Я думаю, что на dispatch_barriers стоит посмотреть. Использование gcd для синхронности для меня более интуитивно понятно, чем использование ключевого слова synchronize, чтобы избежать мутации состояния из нескольких потоков.

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

person amol-c    schedule 01.10.2015

Подход:

Используйте 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)
        }
    }
}
person user1046037    schedule 23.05.2017
comment
Какую версию Swift вы используете? - person user1046037; 13.10.2017
comment
Плохо, я написал комментарий, не пытаясь скомпилировать код. Я думал, что return queue.sync { _array } не будет компилироваться, потому что sync ничего не возвращает. - person derpoliuk; 13.10.2017
comment
Он возвращает массив, и доступ к нему синхронизируется. - person user1046037; 13.10.2017
comment
Доступ к массиву синхронизирован, правда, но никакие операции, выполняемые с возвращаемым массивом, нет. - person Michael Long; 17.11.2017

во-первых, objc_sync_enter не работает

objc_sync_enter(array)
defer {
   objc_sync_exit(array)
}

причина objc_sync_enter / objc_sync_exit не работает с DISPATCH_QUEUE_PRIORITY_LOW

objc_sync_enter - это примитив крайне низкого уровня, который не предназначен для прямого использования. Это деталь реализации старой @synchronized системы в ObjC.

для быстрого, следует использовать так, как сказал @Kirsteins, и я предлагаю синхронизацию вместо async:

private let syncQueue = DispatchQueue(label:"com.test.LockQueue") 
func test(){
    self.syncQueue.sync{
        // thread safe code here
    }
}
person lbsweek    schedule 03.05.2018
comment
последовательная очередь с синхронизацией не вызовет тупик? - person karthik; 18.11.2018
comment
спасибо @karthik, синхронизация действительно привела к тупику в каком-то сценарии. но для моего проекта очередь, в которой он используется, не зависит от ресурсов, так что все в порядке. - person lbsweek; 19.11.2018

Чтобы улучшить принятый ответ, я бы предложил использовать 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()
}
person Vyacheslav    schedule 26.01.2018
comment
это не работает, см. stackoverflow.com/questions/35084754/ - person lbsweek; 03.05.2018