对数组的并发读/写访问中的悬空指针

问题描述 投票:0回答:1

我试图了解我访问数组的方法有什么问题。在我的应用程序中,我遇到了几次不可重现的崩溃,

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

那么,问题是为什么会出现悬垂的足尖呢?也许您知道在对数组执行读/写操作时避免这种情况的最佳实践?

ios swift concurrency
1个回答
0
投票

是的,这个堆栈跟踪、“悬空指针”引用等都是转移注意力的东西。这段代码根本就不是线程安全的。暂时打开 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 消除数据竞争

© www.soinside.com 2019 - 2024. All rights reserved.