并发读写数组时悬空指针问题的原因及解决方案咨询
当多个线程同时对数组进行读写操作时,会直接破坏数组的底层存储结构。比如写入线程触发数组扩容、删除元素或者替换内容时,数组依赖的_ContiguousArrayStorage底层存储可能会被重新分配内存,甚至直接释放。如果此时读取线程还在持有旧存储的指针,当旧内存被释放后,读取线程再访问这个指针就会触发悬空指针问题,也就是你遇到的EXC_BAD_ACCESS KERN_INVALID_ADDRESS崩溃。
你看到的_ContiguousArrayStorage对象释放时引用计数非零的警告,本质是读写操作完全不同步导致的:写入线程已经启动了存储的释放逻辑,但读取线程还在引用这块内存,导致引用计数统计异常,最终引发悬空指针访问。
加锁保护读写操作:用
NSLock、pthread_mutex_t或者GCD串行队列包裹所有数组的读写操作,确保同一时间只有一个线程能访问数组。举个简单的Swift实现例子:let arraySyncQueue = DispatchQueue(label: "com.your.app.array.sync") var threadSafeArray: [Any] = [] func addElement(_ element: Any) { arraySyncQueue.sync { threadSafeArray.append(element) } } func getElement(at index: Int) -> Any? { return arraySyncQueue.sync { guard index < threadSafeArray.count else { return nil } return threadSafeArray[index] } }封装线程安全的数组类:要么直接用iOS 13+提供的
OSAllocatedUnfairLock来封装数组,要么自己写一个线程安全的数组容器,对外只暴露经过同步处理的读写方法,把锁的逻辑隐藏在内部,避免业务代码重复处理同步。尽量单线程操作数组:如果业务场景允许,让数组的读写都在同一个线程(比如UI线程)完成。如果必须跨线程操作,一定要确保每一次读写都经过同步处理,绝对不能让多个线程直接操作未加保护的原数组。
写少读多场景用不可变数组替换:如果写入操作不频繁,可以每次写入时创建一个新的不可变数组,替换原有的数组变量。不过要注意,替换变量的操作本身也需要同步,而且频繁创建新数组会有性能损耗,只适合写少读多的场景。
内容的提问来源于stack exchange,提问作者Gomer Grek

