我试图了解我访问数组的方法有什么问题。在我的应用程序中,我遇到了几次不可重现的崩溃,
EXC_BAD_ACCESS KERN_INVALID_ADDRESS (some not 0 address)
。我认为这与对数组的读/写访问有关。为了进行调查,我编写了与游乐场应用程序代码有点相似的代码:
import Foundation
import CoreBluetooth
let requiredServices: [CBUUID] = [
CBUUID(string: "180A"),
CBUUID(string: "280A"),
CBUUID(string: "380A"),
CBUUID(string: "480A"),
CBUUID(string: "580A"),
CBUUID(string: "680A"),
CBUUID(string: "780A"),
CBUUID(string: "880A"),
]
// Array to investigate
var availableServices = [CBUUID]()
let queues = [
DispatchQueue(label: "test0"),
DispatchQueue(label: "test1"),
DispatchQueue(label: "test2"),
DispatchQueue(label: "test3"),
DispatchQueue(label: "test4"),
DispatchQueue(label: "test5"),
DispatchQueue(label: "test6"),
DispatchQueue(label: "test7"),
DispatchQueue(label: "test8"),
DispatchQueue(label: "test9"),
DispatchQueue(label: "test10")
]
func mainTest() {
print(">>>> Main test")
for i in 0...100 {
for j in 1...10 {
queues[0].async {
for _ in 1...100 {
// Write operation
doAppendAndClear()
}
}
queues[j].async {
for k in 1...100 {
// Read operation
tryToRead()
if i == 100, j == 10, k == 100 {
print(">>>> The end")
}
}
}
}
}
}
func doAppendAndClear() {
availableServices.append(CBUUID(string: "180A"))
availableServices.append(CBUUID(string: "280A"))
availableServices = []
availableServices.append(CBUUID(string: "380A"))
availableServices.append(CBUUID(string: "480A"))
availableServices = []
availableServices.append(CBUUID(string: "580A"))
availableServices.append(CBUUID(string: "680A"))
availableServices = []
availableServices.append(CBUUID(string: "780A"))
availableServices.append(CBUUID(string: "880A"))
availableServices = []
}
func tryToRead() {
let services = availableServices
var available = true
requiredServices.forEach({ available = available && services.contains($0)})
}
mainTest()
我在操场上收到此警告,当我将测试粘贴到应用程序代码中时,也收到相同的警告:
类 _ContigouslyArrayStorage 的对象 0x600000e631e0 被释放,保留计数为非零 2。该对象的 deinit 或从它调用的东西可能创建了对 self 的强引用,该引用超出了 deinit 的寿命,从而导致悬空引用
此外,当我将测试代码粘贴到应用程序中时,应用程序崩溃了。它发生在
tryToRead()
方法中,在它的最后一行:
thread #17, queue = 'test5', stop reason = EXC_BAD_ACCESS (code=1, address=0x97b30d800)
frame #0: 0x000000018c3f2244 libobjc.A.dylib`objc_retain_x8 + 16
frame #1: 0x0000000193172210 libswiftCore.dylib`swift::metadataimpl::ValueWitnesses<swift::metadataimpl::ObjCRetainableBox>::initializeWithCopy(swift::OpaqueValue*, swift::OpaqueValue*, swift::TargetMetadata<swift::InProcess> const*) + 28
frame #2: 0x0000000192ddf844 libswiftCore.dylib`Swift.Array.subscript.read : (Swift.Int) -> τ_0_0 + 232
frame #3: 0x0000000192ddf728 libswiftCore.dylib`protocol witness for Swift.Collection.subscript.read : (τ_0_0.Index) -> τ_0_0.Element in conformance Swift.Array<τ_0_0> : Swift.Collection in Swift + 68
frame #4: 0x0000000192e3fad0 libswiftCore.dylib`protocol witness for Swift.IteratorProtocol.next() -> Swift.Optional<τ_0_0.Element> in conformance Swift.IndexingIterator<τ_0_0> : Swift.IteratorProtocol in Swift + 676
frame #5: 0x0000000192f88164 libswiftCore.dylib`Swift.Sequence.contains(where: (τ_0_0.Element) throws -> Swift.Bool) throws -> Swift.Bool + 664
frame #6: 0x0000000192e767ec libswiftCore.dylib`Swift.Sequence< where τ_0_0.Element: Swift.Equatable>.contains(τ_0_0.Element) -> Swift.Bool + 156
* frame #7: 0x0000000104d2a3d4 MyApp`closure #1 in ContainerVC.tryToRead($0=0x0000000303fde400, available=true, services=2 values) at ContainerVC.swift:202:58
frame #8: 0x0000000104d310d4 MyApp`partial apply for closure #1 in ContainerVC.tryToRead() at <compiler-generated>:0
frame #9: 0x0000000192ea2e70 libswiftCore.dylib`Swift.Sequence.forEach((τ_0_0.Element) throws -> ()) throws -> () + 756
frame #10: 0x0000000104d2a2dc MyApp`ContainerVC.tryToRead(self=0x00000001420c7800) at ContainerVC.swift:202:26
frame #11: 0x0000000104d29c2c MyApp`closure #2 in ContainerVC.mainTest(self=0x00000001420c7800, i=0, j=5) at ContainerVC.swift:166:30
Firebase 中的原始崩溃在堆栈中具有不同的顶行:
Crashed: com.apple.main-thread
0 libswiftCore.dylib 0x2f7178 _swift_release_dealloc + 16
1 libswiftCore.dylib 0x2f8060 bool swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1> >::doDecrementSlow<(swift::PerformDeinit)1>(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 184
那么,问题是为什么会出现悬垂的足尖呢?也许您知道在对数组执行读/写操作时避免这种情况的最佳实践?
是的,这个堆栈跟踪、“悬空指针”引用等都是转移注意力的东西。这段代码根本就不是线程安全的。暂时打开 Thread Sanitizer(又名 TSAN)来尝试您的代码,它会指出此代码中的问题,即“Swift 访问竞争”:
==================
WARNING: ThreadSanitizer: Swift access race (pid=51572)
Modifying access of Swift variable at 0x000110104370 by thread T3:
#0 …
Previous read of size 8 at 0x000110104370 by thread T4:
#0 …
Location is heap block of size 144 at 0x0001101042f0 allocated by main thread:
#0 …
Thread T3 (tid=26151799, running) is a GCD worker thread
Thread T4 (tid=26151800, running) is a GCD worker thread
SUMMARY: ThreadSanitizer: Swift access race (/…/MyApp:arm64+0x100003314) in MyApp.ViewController.availableServices.modify : Swift.Array<__C.CBUUID>+0x84
==================
必须同步对数组的访问,以确保线程安全。所有交互都必须使用某种机制(例如参与者、锁或串行调度队列等)进行同步。
有关我们如何与 Actor 合作的信息,请参阅 WWDC 2022 视频,使用 Swift Concurrency 消除数据竞争