web-dev-qa-db-ja.com

Swift配列に複数のスレッドにまたがる配列を追加すると、問題が発生します(配列はスレッドセーフではないため)-どうすれば回避できますか?

指定されたブロックを配列に追加し、要求に応じて、配列に含まれるすべてのブロックを実行したいと思います。私はこれに似たコードを持っています:

class MyArrayBlockClass {
    private var blocksArray: Array<() -> Void> = Array()

    private let blocksQueue: NSOperationQueue()

    func addBlockToArray(block: () -> Void) {
        self.blocksArray.append(block)
    }

    func runBlocksInArray() {
        for block in self.blocksArray {
            let operation = NSBlockOperation(block: block)
            self.blocksQueue.addOperation(operation)
        }

        self.blocksQueue.removeAll(keepCapacity: false)
    }
}

問題は、addBlockToArrayが複数のスレッド間で呼び出される可能性があるという事実にあります。何が起こっているのかというと、addBlockToArrayは異なるスレッド間ですばやく連続して呼び出され、アイテムの1つだけを追加しているため、runBlocksInArray中に他のアイテムは呼び出されません。

私はこのようなものを試しましたが、機能していないようです:

private let blocksDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

func addBlockToArray(block: () -> Void) {
    dispatch_async(blocksDispatchQueue) {
        self.blocksArray.append(block)
    }
}
16
Andrew

blocksDispatchQueueをグローバルキューとして定義しました。これをSwift 3に更新すると、同等のものは次のようになります。

private let queue = DispatchQueue.global()

func addBlockToArray(block: @escaping () -> Void) {
    queue.async {
        self.blocksArray.append(block)
    }
}

問題は、グローバルキューが同時キューであるため、必要な同期が達成されていないことです。ただし、独自のシリアルキューを作成した場合は、それで問題ありません。 in Swift 3:

private let queue = DispatchQueue(label: "com.domain.app.blocks")

このカスタムキューは、デフォルトではシリアルキューです。したがって、必要な同期を実現できます。

このblocksDispatchQueueを使用してこのキューとのやり取りを同期する場合、allこのblocksArrayとのやり取りは次のようになります。このキューを介して調整されます。また、同じキューを使用して操作を追加するコードをディスパッチします。

func runBlocksInArray() {
    queue.async {
        for block in self.blocksArray {
            let operation = BlockOperation(block: block)
            self.blocksQueue.addOperation(operation)
        }

        self.blocksArray.removeAll()
    }
}

または、 リーダー/ライターパターン を使用して、独自の並行キューを作成することもできます。

private let queue = DispatchQueue(label: "com.domain.app.blocks", attributes: .concurrent)

ただし、リーダーとライターのパターンでは、書き込みはバリアを使用して実行する必要があります(書き込みのシリアルのような動作を実現します)。

func addBlockToArray(block: @escaping () -> Void) {
    queue.async(flags: .barrier) {
        self.blocksArray.append(block)
    }
}

ただし、上記のようにデータを読み取ることができるようになりました。

let foo = queue.sync {
    blocksArray[index]
}

このパターンの利点は、書き込みが同期されることですが、読み取りは相互に同時に発生する可能性があります。この場合、これはおそらく重要ではありません(したがって、単純なシリアルキューで十分です)が、完全を期すために、この読み取り/書き込みパターンを含めます。


別のアプローチはNSLockです。

extension NSLocking {
    func withCriticalSection<T>(_ closure: () throws -> T) rethrows -> T {
        lock()
        defer { unlock() }
        return try closure()
    }
}

したがって:

let lock = NSLock()

func addBlockToArray(block: @escaping () -> Void) {
    lock.withCriticalSection {
        blocksArray.append(block)
    }
}

ただし、上記のようにデータを読み取ることができるようになりました。

let foo = lock.withCriticalSection {
    blocksArray[index]
}

歴史的にはNSLockはパフォーマンスが低いとして常に却下されていましたが、現在ではGCDよりもさらに高速です。


Swift 2つの例を探している場合は、この回答の 前の表現 を参照してください。

27
Rob

スレッド間の同期には、dispatch_sync(_ asyncではない)と独自のディスパッチキュー(グローバルキューではない)を使用します。

class MyArrayBlockClass {
    private var queue = dispatch_queue_create("andrew.myblockarrayclass", nil)

    func addBlockToArray(block: () -> Void) {
        dispatch_sync(queue) {
            self.blocksArray.append(block)
        } 
    }
    //....
}

dispatch_syncは素晴らしくて使いやすく、あなたのケースには十分なはずです(私は現時点ですべてのスレッド同期のニーズに使用しています)が、低レベルのロックとミューテックスを使用することもできます。さまざまな選択肢についてのMikeAshによるすばらしい記事があります: ロック、スレッドセーフ、およびスイフト

3
Teemu Kurppa

シリアルキューを作成し、そのスレッドの配列に変更を加えます。スレッド作成の呼び出しは次のようになります

private let blocksDispatchQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_SERIAL)

そうすれば、今と同じように使用できます。

func addBlockToArray(block: () -> Void) {
    dispatch_async(blocksDispatchQueue) {
        self.blocksArray.append(block)
    }
}
1
Sumeet

詳細

  • Xcode 10.1(10B61)
  • Swift 4.2

解決

import Foundation

class AtomicArray<T> {

    private lazy var semaphore = DispatchSemaphore(value: 1)
    private var array: [T]

    init (array: [T]) { self.array = array }

    func append(newElement: T) {
        wait(); defer { signal() }
        array.append(newElement)
    }

    subscript(index: Int) -> T {
        get {
            wait(); defer { signal() }
            return array[index]
        }
        set(newValue) {
            wait(); defer { signal() }
            array[index] = newValue
        }
    }

    var count: Int {
        wait(); defer { signal() }
        return array.count
    }

    private func wait() { semaphore.wait() }
    private func signal() { semaphore.signal() }

    func set(closure: (_ curentArray: [T])->([T]) ) {
        wait(); defer { signal() }
        array = closure(array)
    }

    func get(closure: (_ curentArray: [T])->()) {
        wait(); defer { signal() }
        closure(array)
    }

    func get() -> [T] {
        wait(); defer { signal() }
        return array
    }
}

extension AtomicArray: CustomStringConvertible {
    var description: String { return "\(get())"}
}

使用法

基本的な考え方は、通常の配列の構文を使用することです

let atomicArray = AtomicArray(array: [3,2,1])

 print(atomicArray)
 atomicArray.append(newElement: 1)

 let arr = atomicArray.get()
 print(arr)
 atomicArray[2] = 0

 atomicArray.get { currentArray in
      print(currentArray)
 }

 atomicArray.set { currentArray -> [Int] in
      return currentArray.map{ item -> Int in
           return item*item
      }
 }
 print(atomicArray)

使用結果

enter image description here

完全なサンプル

import UIKit

class ViewController: UIViewController {

    var atomicArray = AtomicArray(array: [Int](repeating: 0, count: 100))

    let dispatchGroup = DispatchGroup()

    override func viewDidLoad() {
        super.viewDidLoad()

        arrayInfo()

        sample { index, dispatch in
            self.atomicArray[index] += 1
        }

        dispatchGroup.notify(queue: .main) {
            self.arrayInfo()
            self.atomicArray.set { currentArray -> ([Int]) in
                return currentArray.map{ (item) -> Int in
                    return item + 100
                }
            }
           self.arrayInfo()
        }

    }

    private func arrayInfo() {
        print("Count: \(self.atomicArray.count)\nData: \(self.atomicArray)")
    }

    func sample(closure: @escaping (Int,DispatchQueue)->()) {

        print("----------------------------------------------\n")

        async(dispatch: .main, closure: closure)
        async(dispatch: .global(qos: .userInitiated), closure: closure)
        async(dispatch: .global(qos: .utility), closure: closure)
        async(dispatch: .global(qos: .default), closure: closure)
        async(dispatch: .global(qos: .userInteractive), closure: closure)
    }

    private func async(dispatch: DispatchQueue, closure: @escaping (Int,DispatchQueue)->()) {

        for index in 0..<atomicArray.count {
            dispatchGroup.enter()
            dispatch.async {
                closure(index,dispatch)
                self.dispatchGroup.leave()
            }
        }
    }
}

完全なサンプル結果

enter image description here

0

NSOperationQueue自体はスレッドセーフなので、suspendedをtrueに設定し、任意のスレッドから必要なすべてのブロックを追加してから、suspendedをfalseに設定してすべてのブロックを実行できます。 。

0
Jack Lawrence