指定されたブロックを配列に追加し、要求に応じて、配列に含まれるすべてのブロックを実行したいと思います。私はこれに似たコードを持っています:
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)
}
}
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つの例を探している場合は、この回答の 前の表現 を参照してください。
スレッド間の同期には、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によるすばらしい記事があります: ロック、スレッドセーフ、およびスイフト
シリアルキューを作成し、そのスレッドの配列に変更を加えます。スレッド作成の呼び出しは次のようになります
private let blocksDispatchQueue = dispatch_queue_create("SynchronizedArrayAccess", DISPATCH_QUEUE_SERIAL)
そうすれば、今と同じように使用できます。
func addBlockToArray(block: () -> Void) {
dispatch_async(blocksDispatchQueue) {
self.blocksArray.append(block)
}
}
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)
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()
}
}
}
}
NSOperationQueue
自体はスレッドセーフなので、suspended
をtrueに設定し、任意のスレッドから必要なすべてのブロックを追加してから、suspended
をfalseに設定してすべてのブロックを実行できます。 。