nat*_*ft1 4 multithreading ios swift
我有一个 AtomicList 类型:
public struct AtomicList<Element> {
public var values: [Element] = []
private let queue = DispatchQueue(label: UUID().uuidString)
public var last: Element? {
self.queue.sync { return self.values.last }
}
public mutating func append(item: Element) {
self.queue.sync {
self.values.append(item)
}
}
}
Run Code Online (Sandbox Code Playgroud)
还有一个不稳定的失败测试,应该验证它是线程安全的:
class AtomicListTests: XCTestCase {
func testReadingAndWritingOnDifferentThreads() {
var arrayA = AtomicList<Int>()
var arrayB = AtomicList<Int>()
let expectation = expectation(description: "thread safety test")
expectation.expectedFulfillmentCount = 10000
for i in 0..<expectation.expectedFulfillmentCount {
DispatchQueue.global().async {
arrayA.append(item: i)
if let last = arrayA.last {
arrayB.append(item: last)
}
expectation.fulfill()
}
}
wait(for: [expectation], timeout: 5.0)
XCTAssertEqual(arrayA.values.count, arrayB.values.count)
}
}
Run Code Online (Sandbox Code Playgroud)
我有两个问题:
注意:我注意到有几个解决方案可以解决此问题并使其通过测试,例如:
但我仍然不完全理解为什么现有代码不起作用,以及为什么上面的前两个解决方案使它通过。
这是否是检查该类型是否线程安全的有效测试?
不会。仅仅因为竞争条件未能导致症状并不意味着不存在竞争条件。要测试测试期间发生的基本内存竞争条件,您需要使用线程清理程序。这会检测您的代码,以确保每个跨线程内存访问都受到锁的保护。这仍然不能证明不存在低级竞争条件,因为它可能不会执行所有代码路径(尽管它会检测不受保护的访问,即使它们碰巧没有冲突,这就是它的要点)。
所有这些仍然并不意味着没有更高级别的竞争条件。请参阅 bbum 的回答,了解为什么 Objective-Catomic不提供高级线程安全性,即使它消除了所有低级数据竞争。以这种方式证明系统是线程安全的可能是不可能的。
对于为什么此代码不起作用的问题,这不是访问值类型的有效方法。值类型在传递给函数或变异时会被复制。它们是价值观。它们不是一个可以有多个引用指向同一个引用的对象。从多个线程访问同一结构不是定义的行为,并且破坏了写时复制优化。您在内部数组周围有一个锁,但在 AtomicList 结构周围没有锁。(如果编译器在此方面失败就好了,但它会破坏太多进行单个结构修改的常见 GCD 模式。)
这意味着对内部数组的访问是同步的,但对结构体的修改不是同步的。你的问题比偶尔的长度不匹配要大得多。价值观也是错误的。如果我运行 100 次并检查arrayB,前几个元素通常是错误的。有时是[1,1,2],有时是[1,0,2],等等。
正确的工具是actor. 但对于 GCD 来说,这仅对引用类型的类有意义,因此锁可以工作。