programing

Swift에서 스레드 안전 배열 만들기

telecom 2023. 11. 6. 21:39
반응형

Swift에서 스레드 안전 배열 만들기

스위프트에 스레딩 문제가 있습니다.몇 개의 객체가 들어 있는 배열이 있습니다.딜러를 통해 클래스는 매초마다 새 개체를 가져옵니다.그 다음에는 객체가 이미 배열되어 있는지 확인해야 하므로 객체를 업데이트해야 합니다. 그렇지 않으면 새 객체를 삭제/추가해야 합니다.

새 개체를 추가하면 먼저 네트워크를 통해 데이터를 가져와야 합니다.이것은 블록을 통해 핸드헬드됩니다.

문제는 이 작업을 동기화하는 방법입니다.

dispatch_semaphore를 시도해 보았지만, 이것은 블록이 끝날 때까지 UI를 차단합니다.

블록이 현재 실행 중인지 확인하고 비교 방법을 건너뛰는 간단한 boole 변수도 시도해 보았습니다.

하지만 두 방법 모두 이상적이지는 않습니다.

어레이를 관리하는 가장 좋은 방법은 무엇입니까? 어레이에 중복 데이터가 있는 것은 원하지 않습니다.

스위프트용 업데이트

스레드 안전 액세스를 위해 권장되는 패턴은 디스패치를 사용하는 것입니다.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

왜 사람들이 그렇게 간단한 것에 대해 그렇게 복잡한 접근을 하는지 모르겠습니다.

  • 욕하지마DispatchQueues잠글 때의사용.queue.sync잠금을 획득하고 잠금 상태에서 다른 스레드에 작업을 전송하는 것에 불과합니다(DispatchGroup) 기다립니다.불필요할 뿐만 아니라 잠금 상태에 따라 부작용이 발생할 수 있습니다.GCD 소스에서 직접 찾아보실 수 있습니다.

    또한 GCD는 새로운 구조화된 동시성 API와 잘 섞이지 않습니다!

  • 사용하지않습니다.objc_sync_enter/exit, 그것들은 ObjCs에 의해 사용됩니다.@synchronized이것은 또한 불필요한 ObjC 대응물에 Swift 컬렉션을 암시적으로 연결할 것입니다.그리고 레거시 API입니다.

자물쇠를 정의하고 수집 접근을 보호하면 됩니다.

var lock = NSLock()
var a = [1, 2, 3]

lock.lock()
a.append(4)
lock.unlock()

삶을 좀 더 편안하게 하고 싶다면 작은 확장자를 정의하세요.

extension NSLock {

    @discardableResult
    func with<T>(_ block: () throws -> T) rethrows -> T {
        lock()
        defer { unlock() }
        return try block()
    }
}

let lock = NSLock()
var a = [1, 2, 3]

lock.with { a.append(4) }

A를 정의할 수도 있습니다.@propertyWrapper회원으로 삼기 위해var원자의

@propertyWrapper
struct Atomic<Value> {

    private let lock = NSLock()
    private var value: Value

    init(default: Value) {
        self.value = `default`
    }

    var wrappedValue: Value {
        get {
            lock.lock()
            defer { lock.unlock() }
            return value
        }
        set {
            lock.lock()
            defer { lock.unlock() }
            value = newValue
        }
    }
}

마지막으로 원시 원자형의 경우 스위프트 아토믹스도 있습니다.

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

업데이트 이것은 스위프트3용으로 업데이트된 것과 동일한 코드입니다.

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

이 문제에 대한 제 접근 방식은 직렬 디스패치 큐를 사용하여 박스 배열에 대한 액세스를 동기화하는 것이었습니다.인덱스에서 값을 가져오려고 할 때 스레드가 차단되고 대기열이 매우 바쁘지만 잠금의 문제이기도 합니다.

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

세부 사항

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

사소한 세부 사항: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...
}

행위자가 포함된 스레드 세이프 데이터 구조

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바뀔 수도 있었습니다.

이러한 읽기 후 쓰기 작업은 일관성을 유지하기 위해 원자적이어야 하므로 대신 액터에 대한 메소드로 정의해야 합니다.

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

이렇게 하면 통과된 행위자는 각 보류 지점이 아닌 전체 통화 중에 격리됩니다.이 기능을 호출하려면 서스펜션 포인트가 하나만 필요합니다.

스위프트-니오 & 베이퍼 스위프트

Swift-Nio(또는 Swift-Nio를 기반으로 하는 Vapor Swift)를 사용하는 분들을 위해 이 문제에 대한 내장 솔루션이 있습니다.

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

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

동일한 방법을 사용해야 합니다.Lock개체를 수정할 때 사용합니다.Array목적어Dictionary, 등).

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

스위프트 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
        }
    }
}

dispatch_barrier는 조사할 가치가 있다고 생각합니다.여러 스레드에서 상태가 변형되지 않도록 동기화 키워드를 사용하는 것보다 동기화에 gcd를 사용하는 것이 더 직관적입니다.

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

접근 방법:

사용하다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)
        }
    }
}

먼저 objc_sync_enter가 작동하지 않습니다.

objc_sync_enter(array)
defer {
   objc_sync_exit(array)
}

reason objc_sync_enter / objc_sync_exit이 DISPATISPATION_QUE_PRIORITE_LOW에서 작동하지 않습니다.

objc_sync_enter는 극도로 낮은 수준의 프리미티브이므로 직접 사용하지 않습니다.ObjC의 구 @synchronized 시스템 구현 세부 사항입니다.

@Kirsteins가 말한 것처럼, swift는 다음과 같이 사용해야 합니다. 그리고 저는 비동기 대신 sync를 제안합니다.

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

배열과 스레드 안전한 상호 작용을 원한다면 액세스를 동기화해야 합니다.제안된 대안이 많습니다(몇 가지는 생략). 따라서 다양한 동기화 대안을 조사해 보겠습니다.

  1. 시리얼 디스패치 대기열:간단하면서도 직관적인 GCD 패턴입니다.

  2. 동시 대기열이 있는 판독기 작성기 패턴:이는 직렬 디스패치 대기열 패턴을 우아하게 개선한 것으로, 비동기 "쓰기"가 있는 동시 대기열을 사용하여(발신자가 쓰기가 끝날 때까지 기다리지 않도록) 장벽이 있지만("쓰기"와 동시에 상호작용을 방지하기 위해), 동시 "읽기"를 제공합니다("읽기" 중에 더 큰 동시성을 허용합니다).이는 정교하고 매력적인 패턴이지만 실제로는 동시 "읽기"와 비동기 "쓰기"의 이점이 GCD 오버헤드보다 클 경우에만 유용합니다.

  3. 잠금 장치:

    • NSLock는 빠르고 간단한 잠금 메커니즘으로 대부분의 시나리오에서 GCD 대체품보다 성능이 뛰어납니다.

      extension NSLocking {
          func synchronized<T>(_ block: () throws -> T) rethrows -> T {
              lock()
              defer { unlock() }
              return try block()
          }
      }
      
    • os_unfair_lock는 또 다른 잠금 메커니즘으로, 그것은 훨씬 더 빠릅니다.NSLock, 하지만 스위프트가 쓰기엔 좀 더 복잡합니다https://stackoverflow.com/a/66525671/1271826 를 참조하십시오.그러나 성능이 무엇보다 중요한 드문 경우에는 불공정한 잠금 장치가 설득력 있는 해결책이 됩니다.

  4. 목표-Cobjc_sync_enter그리고.objc_sync_exitAPI: 이것은 스위프트 세계에서는 실질적인 관심사가 아닙니다.

  5. 세마포어:개념적으로는 잠금 기반 접근법과 유사하지만 일반적으로 잠금 기반 접근법보다 느리기 때문에 이 대화에서는 무시할 수 있습니다.

  6. 액터스: Swift 5.5 동시성 시스템에서 제공하는 동기화 메커니즘Swift 배우와 함께 가변 상태 보호를 참조하십시오.

간단히 말해서, 만약 사용한다면.async-await, 배우가 논리적인 대안입니다.아직 스위프트 동시성 시스템을 채택하지 않았다면 잠금 기반 접근법(간단하고 빠른)이나 드물게는 GCD 리더-라이터 접근법에 끌릴 것입니다.

실제로 대부분의 사용 사례에서 동기화 메커니즘을 선택하는 것은 중요하지 않습니다. (그리고 성능 차이가 중요해질 정도로 많은 동기화를 수행하는 경우, 특정 메커니즘을 사용하기 전에 동기화 포인트 수를 줄이는 방법을 고려해 볼 수 있습니다.)그렇다 치더라도, 오래된 동기화 메커니즘(세마포어,objc_sync_enter, 더 이상 고려하지 않을 것입니다.


가능한 동기화 메커니즘의 개요를 설명한 다음 다음 질문은 동기화를 수행하는 수준입니다.구체적으로, 전체 어레이에 대한 속성 래퍼(property wraper)가 두 번 이상 제안되었습니다.여기는 항상 동기화하기에 잘못된 위치입니다.속성 래퍼 접근 방식은 배열에 대한 원자적 액세스(thread-safety와는 전혀 동일하지 않음)를 제공하지만 일반적으로 더 높은 수준의 추상화가 필요합니다.예를 들어, 한 스레드가 요소를 추가하고 다른 스레드가 읽기 또는 제거하는 경우 배열의 개별 액세스뿐만 아니라 이러한 고급 작업을 각각 동기화하기를 원하는 경우가 많습니다.

수락된 답변을 개선하기 위해 다음과 같이 연기를 사용할 것을 제안합니다.

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

스위프트 스레드 세이프 컬렉션

Swift에서 무언가(예: Collection) 스레드를 안전하게 만드는 가장 일반적인 아이디어는 다음과 같습니다.

  1. 사용자 지정(로컬) 동시 대기열
  2. 동기 판독.다음을 통해 중요 섹션(공유 리소스) 읽기sync
  3. 장벽이 있는 비동기 쓰기

[스위프트 스레드 세이프 싱글톤]

스레드 세이프(thread safe)와 동시 읽기를 차단하지 않는 훌륭한 답변이 있습니다. https://stackoverflow.com/a/15936959/2050665

Objective C에 기재되어 있지만 스위프트로 포팅하는 것은 사소한 일입니다.

@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 에 대한 크레딧 제공

언급URL : https://stackoverflow.com/questions/28191079/create-thread-safe-array-in-swift

반응형