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
相关产品推荐
相关产品推荐

