跨多个线程向 Swift 数组添加项会导致问题(因为数组不是线程安全的) - 我该如何解决? [英] Adding items to Swift array across multiple threads causing issues (because arrays aren't thread safe) - how do I get around that?

查看:29
本文介绍了跨多个线程向 Swift 数组添加项会导致问题(因为数组不是线程安全的) - 我该如何解决?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想将给定的块添加到数组中,然后在请求时运行该数组中包含的所有块.我有类似这样的代码:

I want to add given blocks to an array, and then run all the blocks contained in the array, when requested. I have code similar to this:

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 被不同线程快速连续调用,并且只附加其中一个项目,因此在 runBlocksInArray 期间不会调用另一个项目.

The problem comes with the fact that addBlockToArray can be called across multiple threads. What's happening is addBlockToArray is being called in quick succession across different threads, and is only appending one of the items, and so the other item is therefore not getting called during runBlocksInArray.

我试过这样的事情,但似乎不起作用:

I've tried something like this, which doesn't seem to be working:

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 更新这个,相当于:

You have defined your blocksDispatchQueue to be a global queue. Updating this for Swift 3, the equivalent is:

private let queue = DispatchQueue.global()

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

问题是全局队列是并发队列,所以你没有实现你想要的同步.但是,如果您创建了自己的串行队列,那就没问题了,例如在 Swift 3 中:

The problem is that global queues are concurrent queues, so you're not achieving the synchronization you want. But if you created your own serial queue, that would have been fine, e.g. in Swift 3:

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

默认情况下,此自定义队列是串行队列.这样您就可以实现您想要的同步.

This custom queue is, by default, a serial queue. Thus you will achieve the synchronization you wanted.

注意,如果你使用这个blocksDispatchQueue来同步你与这个队列的交互,所有与这个blocksArray的交互应该通过这个队列来协调,例如还调度代码以使用相同的队列添加操作:

Note, if you use this blocksDispatchQueue to synchronize your interaction with this queue, all interaction with this blocksArray should be coordinated through this queue, e.g. also dispatch the code to add the operations using the same queue:

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

        self.blocksArray.removeAll()
    }
}

<小时>

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


Alternatively, you can also employ the reader/writer pattern, creating your own concurrent queue:

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

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

But in the reader-writer pattern, writes should be performed using barrier (achieving a serial-like behavior for writes):

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

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

But you can now read data, like above:

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

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

The benefit of this pattern is that writes are synchronized, but reads can occur concurrently with respect to each other. That's probably not critical in this case (so a simple serial queue would probably be sufficient), but I include this read-writer pattern for the sake of completeness.

另一种方法是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)
    }
}

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

But you can now read data, like above:

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

过去,NSLock 总是被认为性能较差,但现在它甚至比 GCD 还要快.

Historically NSLock was always dismissed as being less performant, but nowadays it is even faster than GCD.

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

If you're looking for Swift 2 examples, see the previous rendition of this answer.

这篇关于跨多个线程向 Swift 数组添加项会导致问题(因为数组不是线程安全的) - 我该如何解决?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆