You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift 基于NSLock实现结构体线程安全数组随机崩溃问题排查

线程安全struct数组实现随机崩溃问题

我正尝试以最高效、安全的方式实现一套由单元测试覆盖的线程安全数组组件。
目前我优先选择使用struct实现数组,以保留值类型特性,而非使用引用类型。
但运行下述测试用例时,仍会出现无法解释的随机崩溃,崩溃现场截图如下:
崩溃截图

SafeArray实现代码

public struct SafeArray<T>: RangeReplaceableCollection {
    public typealias Element = T
    public typealias Index = Int
    public typealias SubSequence = SafeArray<T>
    public typealias Indices = Range<Int>

    private var array: [T]
    
    private var locker = NSLock()
    private func lock() { locker.lock() }
    private func unlock() { locker.unlock() }

    // MARK: - Public methods

    // MARK: - Initializers
    public init<S>(_ elements: S) where S: Sequence, SafeArray.Element == S.Element {
        array = [S.Element](elements)
    }

    public init() { self.init([]) }

    public init(repeating repeatedValue: SafeArray.Element, count: Int) {
        let array = Array(repeating: repeatedValue, count: count)
        self.init(array)
    }
}

extension SafeArray {
    // Single  action

    public func get() -> [T] {
        lock(); defer { unlock() }
        return Array(array)
    }

    public mutating func set(_ array: [T]) {
        lock(); defer { unlock() }
        self.array = Array(array)
    }
}

单元测试代码

final class ConcurrencyTests: XCTestCase {
    
    private let concurrentQueue1 = DispatchQueue.init(label: "concurrentQueue1",
                                                      qos: .background,
                                                      attributes: .concurrent,
                                                      autoreleaseFrequency: .inherit,
                                                      target: nil)

    private let concurrentQueue2 = DispatchQueue.init(label: "concurrentQueue2",
                                                      qos: .background,
                                                      attributes: .concurrent,
                                                      autoreleaseFrequency: .inherit,
                                                      target: nil)

    
    private var safeArray = SafeArray(["test"])

    func wait(for expectations: XCTestExpectation, timeout seconds: TimeInterval) {
        wait(for: [expectations], timeout: seconds)
    }

    func waitForMainRunLoop() {
        let mainRunLoopExpectation = expectation(description: "mainRunLoopExpectation")
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { mainRunLoopExpectation.fulfill() }
        wait(for: mainRunLoopExpectation, timeout: 0.5)
    }

    func waitFor(_ timeout: TimeInterval) {
        let mainRunLoopExpectation = expectation(description: "timeoutExpectation")
        DispatchQueue.main.asyncAfter(deadline: .now() + timeout) { mainRunLoopExpectation.fulfill() }
        wait(for: mainRunLoopExpectation, timeout: timeout + 0.5)
    }
    
    override func setUpWithError() throws {
        try super.setUpWithError()
        safeArray = SafeArray(["test"])
    }

    func testSafeArrayGet() {
        var thread1: Thread!
        var thread2: Thread!
        
        concurrentQueue1.async {
            thread1 = Thread.current
            let startTime = Date()
            for i in 0...1_000_000 {
                self.safeArray.set(["modification"])
                print("modification \(i)")
            }
            print("time modification: \(Date().timeIntervalSince(startTime))")
        }

        concurrentQueue2.async {
            thread2 = Thread.current
            let startTime = Date()
            for i in 0...1_000_000 {
                let _ = self.safeArray.get()
                print("read \(i)")
            }
            print("time read: \(Date().timeIntervalSince(startTime))")
        }

        waitFor(10)

        XCTAssert(!thread1.isMainThread && !thread2.isMainThread)
        XCTAssert(thread1 != thread2)
    }
}

补充复现场景

即便改用class实现简单的线程安全逻辑,依然会触发崩溃,下述极简测试用例同样会出现崩溃:

class TestClass {
        var test = ["test"]
        let nsLock = NSLock()
        
        func safeSet(_ string: String) {
            nsLock.lock()
            test[0] = string // 此处触发崩溃
            nsLock.unlock()
        }
    }
    
    func testStructThreadSafety() {
        let testClass = TestClass()
        
        DispatchQueue.concurrentPerform(iterations: 1_000_000) { i in
            testClass.safeSet("modification \(i)")
            let _ = testClass.test[0]
        }
        
        XCTAssert(true)
    }
待解决问题
  • 上述代码出现崩溃的根本原因是什么?
  • 现有实现存在哪些错误?

备注:如果将SafeArray改为class引用类型实现则不会触发崩溃,但我更希望保留struct值类型的实现方案。

内容的提问来源于stack exchange,提问作者Xys

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 20:42:16