Swift - GCD 可变数组多线程发出“在枚举时发生突变”

Posted

技术标签:

【中文标题】Swift - GCD 可变数组多线程发出“在枚举时发生突变”【英文标题】:Swift - GCD mutable array multiple threads issue "mutated while being enumerated" 【发布时间】:2015-04-28 01:11:21 【问题描述】:

我目前正在开发一款球体从天而降的游戏。在收集球体时,您会获得积分,并且在获得一定数量的积分后,所有球体都会加速到另一个速度。

    新球体不断添加到阵列中(每个 SKNode 内有 4 个球体)。 当它们要加速时,我会遍历数组以提高所有它们的速度。 当球体从屏幕上掉下来时,我将它们从阵列中移除。
class GameScene: SKScene, SKPhysicsContactDelegate 
...
var allActiveNodes = Array<SKNode>()
private let concurrentNodesQueue = dispatch_queue_create(
    "com.SphereHunt.allActiveNodesQueue", DISPATCH_QUEUE_CONCURRENT)
...

//1. This is where the new spheres are added to the Array via a new thread
func addSpheres(leftSphere: Sphere, middleLeftSphere: Sphere, middleRightSphere: Sphere, rightSphere: Sphere)
...
dispatch_barrier_async(self.concurrentNodesQueue)
        self.allActiveNodes.append(containerNode)
        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
        dispatch_async(queue) 
//Set the new spheres in motion
            self.runPastAvatar(containerNode)
        
    

//2. This function starts a thread that will increase the speed of all active spheres
    func increaseSpeed20percent()
        durationPercentage = durationPercentage * 0.8
        dispatch_sync(self.concurrentNodesQueue)
        let copyAllActiveNodes = self.allActiveNodes
        let count = copyAllActiveNodes.count

        for index in 0...count-1
            let node = copyAllActiveNodes[index]
            node.removeAllActions()
            self.runPastAvatar(node)
        
    

//3. This method removes the sphere that is not in screen anymore from the Array
    func removeLastNode(node: SKNode)
    dispatch_barrier_async(self.concurrentNodesQueue)
            self.allActiveNodes.removeAtIndex(0)
            node.removeFromParent()
            println("Removed")
            
    

我不确定我是否正确理解了 GCD,我尝试了多种解决方案,这是我确信会起作用的解决方案。我总是以同样的错误信息告终:

*** Terminating app due to uncaught exception 'NSGenericException', 
reason: '*** Collection <__NSArrayM: 0x17004c9f0> was mutated while being enumerated.'

如何让线程在处理数组时不相互干扰?

【问题讨论】:

【参考方案1】:

我不确定这是否是问题,但来自以下文件:

func dispatch_sync(_ queue: dispatch_queue_t,
             _ block: dispatch_block_t)

与 dispatch_async 不同,不会在目标上执行保留 队列。因为对该函数的调用是同步的,所以它“借用”了 调用者的参考。此外,没有对 Block_copy 执行 块。

> 作为优化,此函数调用当前块 尽可能线程。

我在这里加粗了重要的部分。为什么不用dispatch_barrier_sync 来调用循环。

【讨论】:

我尝试使用 dispatch_barrier_sync 调用循环,但问题仍然存在。 我注释掉了与数组有关的所有内容,但仍然收到相同的错误消息。这很奇怪,因为没有其他数组是这样操作的。我该如何调试呢?错误消息“Collection <__nsarraym:> ...”显示了数组的 ID,但是在查看代码时,数组没有得到任何 ID。我怎样才能找到背后处理的特定数组,从而给出此错误消息? 我把所有的东西都注释掉了。我现在只启动 1 个添加更多球体的线程,然后休眠一段时间。当线程休眠时,我总是会收到相同的错误消息,除了休眠线程之外没有其他任何东西在运行。线程内的睡眠功能会不会有任何问题?但是这样的事情不会产生“<__nsarraym:> 在被枚举时发生了变异。”【参考方案2】:

我的问题是我使用线程睡眠解决方案在时间间隔内发射新球体。这是一个糟糕的选择,但我认为不应该产生这样的错误消息。我使用 NSTimer 在一个时间间隔内发射新球体来解决它。这给游戏带来了一点延迟,但它更强大并且不会崩溃。接下来是找出如何使用 NSTimer 而不会在游戏中造成这样的延迟!

【讨论】:

以上是关于Swift - GCD 可变数组多线程发出“在枚举时发生突变”的主要内容,如果未能解决你的问题,请参考以下文章

Swift - 多线程实现方式 - Grand Central Dispatch(GCD)

GCD多线程在swift中的变化

IOS - 总结下swift使用GCD 多线程GCD和DispatchQueue

Swift多线程:GCD进阶,单例信号量任务组

Swift系列三十一 - 多线程

多线程之GCD