Swift:结构线安全阵列与NSLOCK崩溃

发布于 2025-02-07 20:49:28 字数 4933 浏览 2 评论 0原文

我正在尝试以最有效,最安全的方式实现线程安全阵列组件,并得到单位测试的支持。

到目前为止,我更喜欢struct数组,而不是保留一个值类型,而不是参考类型。

但是,当我在下面运行测试时,我仍然会有随机崩溃,但我没有解释:

“在此处输入图像说明”

这是我的thread> threadssafe array class:andray class:and

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)
    }
}

xcunittest code:

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)
    }
}

edit:editi借助课程和简单的方法使其线程安全,我崩溃了。这是一个非常简单的测试,崩溃了:

class TestClass {
        var test = ["test"]
        let nsLock = NSLock()
        
        func safeSet(_ string: String) {
            nsLock.lock()
            test[0] = string // crash
            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)
    }

为什么会崩溃?我在做什么错?

请注意,如果我将其作为我不会崩溃,但是我希望将其保留为结构。

I'm trying to implement a thread-safe array component in the most efficient and safe way, backed by unit tests.

So far, I would prefer a struct array, to keep a value type and not a reference type.

But when I run the test below, I still have random crashes that I don't explain :

enter image description here

Here's my ThreadSafe array class :

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)
    }
}

And here's my XCUnitTest code :

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)
    }
}

Edit: Event with a class and a simple approach to make it thread safe, I get a crash. Here's a very simple test that crashes :

class TestClass {
        var test = ["test"]
        let nsLock = NSLock()
        
        func safeSet(_ string: String) {
            nsLock.lock()
            test[0] = string // crash
            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)
    }

Why is it crashing? What am I doing wrong?

Note that if I make it a class I don't get crashes, but I would prefer to keep it a struct.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文