在多个线程中向Swift阵列添加项目导致问题(因为数组不是线程安全的) - 我该如何解决这个问题?

And*_*rew 16 arrays multithreading read-write grand-central-dispatch 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

问题在于可以跨多个线程调用addBlockToArray.发生的事情是addBlockToArray在不同的线程中快速连续调用,并且只附加其中一个项目,因此在runBlocksInArray期间不会调用另一个项目.

我尝试过这样的东西,但似乎不起作用:

private let blocksDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

func addBlockToArray(block: () -> Void) {
    dispatch_async(blocksDispatchQueue) {
        self.blocksArray.append(block)
    }
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*Rob 26

您已将自己定义blocksDispatchQueue为全局队列.为Swift 3更新这个,相当于:

private let queue = DispatchQueue.global()

func addBlockToArray(block: @escaping () -> Void) {
    queue.async {
        self.blocksArray.append(block)
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是全局队列是并发队列,因此您无法实现所需的同步.但是如果你创建了自己的串行队列,那就好了,例如在Swift 3中:

private let queue = DispatchQueue(label: "com.domain.app.blocks")
Run Code Online (Sandbox Code Playgroud)

默认情况下,此自定义队列是串行队列.因此,您将实现所需的同步.

请注意,如果您使用此方法blocksDispatchQueue来同步与此队列的交互,则应通过此队列协调与此交互的所有交互blocksArray,例如,还要调度代码以使用相同的队列添加操作:

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

        self.blocksArray.removeAll()
    }
}
Run Code Online (Sandbox Code Playgroud)

或者,您也可以使用读取器/写入器模式,创建自己的并发队列:

private let queue = DispatchQueue(label: "com.domain.app.blocks", attributes: .concurrent)
Run Code Online (Sandbox Code Playgroud)

但是在读写器模式中,应该使用barrier执行写入(实现写入的类似串行的行为):

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

但您现在可以读取数据,如上所述:

let foo = queue.sync {
    blocksArray[index]
}
Run Code Online (Sandbox Code Playgroud)

这种模式的好处是写入是同步的,但读取可以相互发生.在这种情况下,这可能并不重要(因此简单的串行队列可能就足够了),但为了完整起见,我包含了这个读写器模式.

如果您正在寻找Swift 2示例,请参阅此答案的先前版本.


Tee*_*ppa 3

对于线程之间的同步,请使用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)
        } 
    }
    //....
}
Run Code Online (Sandbox Code Playgroud)

dispatch_sync很好而且易于使用,应该足以满足您的情况(我目前使用它来满足所有线程同步需求),但您也可以使用较低级别的锁和互斥锁。Mike Ash 有一篇很棒的文章,介绍了不同的选择:锁、线程安全和 Swift

  • 更新对象时,“dispatch_async”(或“dispatch_barrier_async”)就可以了。仅对于读取,您必须使用“dispatch_sync”。 (3认同)