Hon*_*ney 10 arrays multithreading struct value-type swift
阅读本文我了解到:
不共享值类型的实例:每个线程都有自己的副本.*这意味着每个线程都可以读取和写入其实例,而不必担心其他线程在做什么.
然后我被带到这个答案及其评论
并被告知:
一个数组本身不是线程安全的,正在从多个线程访问,因此必须同步所有交互.
关于每个线程都有自己的副本,我被告知
如果一个线程正在更新一个数组(可能是因为你可以看到来自另一个队列的编辑),那根本就不适用
根本不适用 < - 为什么不呢?
我最初认为所有这一切都发生了,因为数组即一个值类型被包装成一个类但令我惊讶的是我被告知不是真的!所以我又回到了Swift 101:D
Rob*_*Rob 23
根本问题是"每个线程都有自己的副本"的解释.
是的,我们经常使用值类型来确保线程安全,方法是为每个线程提供自己的对象副本(例如数组).但这与声称值类型保证每个线程都将获得自己的副本不同.
具体来说,使用闭包,多个线程可以尝试改变相同的值类型对象.下面是一个代码示例,它显示了一些与Swift Array值类型交互的非线程安全代码:
let queue = DispatchQueue.global()
var employees = ["Bill", "Bob", "Joe"]
queue.async {
let count = employees.count
for index in 0 ..< count {
print("\(employees[index])")
Thread.sleep(forTimeInterval: 1)
}
}
queue.async {
Thread.sleep(forTimeInterval: 0.5)
employees.remove(at: 0)
}
Run Code Online (Sandbox Code Playgroud)
(你通常不会添加sleep调用;我只是将它们添加到显示竞争条件,否则难以重现.你也不应该在没有同步的情况下从这样的多个线程变异对象,但我这样做是为了说明问题.)
在这些async调用中,您仍然指的是employees之前定义的相同数组.因此,在这个特定的例子中,我们将看到它输出"Bill",它将跳过"Bob"(即使它被删除了"Bill"),它将输出"Joe"(现在是第二个项目),并且然后它会崩溃尝试访问现在只剩下两个项目的数组中的第三个项目.
现在,我在上面说明的一点是单个值类型可以被一个线程突变,同时被另一个线程使用,从而违反了线程安全性.在编写非线程安全的代码时,实际上有一系列更基本的问题可以表现出来,但上面只是一个有点人为的例子.
但是,您可以employees通过向第一个async调用添加"捕获列表"来确保此单独的线程获得其自己的数组副本,以指示您要使用原始employees数组的副本:
queue.async { [employees] in
...
}
Run Code Online (Sandbox Code Playgroud)
或者,如果将此值类型作为参数传递给另一个方法,则会自动获得此行为:
doSomethingAsynchronous(with: employees) { result in
...
}
Run Code Online (Sandbox Code Playgroud)
在这两种情况中的任何一种情况下,您将享受值语义并查看原始数组的副本(或写入时复制),尽管原始数组可能已在其他位置发生过变异.
最重要的是,我的观点仅仅是值类型并不能保证每个线程都有自己的副本.该Array类型不是(也没有许多其他可变值类型)线程安全的.但是,像所有值类型一样,Swift提供了简单的机制(其中一些是完全自动和透明的),它们将为每个线程提供自己的副本,从而使编写线程安全的代码变得更加容易.
这是另一个使用其他值类型的示例,使问题更加明显.这是一个无法编写线程安全代码返回语义无效对象的示例:
let queue = DispatchQueue.global()
struct Person {
var firstName: String
var lastName: String
}
var person = Person(firstName: "Rob", lastName: "Ryan")
queue.async {
Thread.sleep(forTimeInterval: 0.5)
print("1: \(person)")
}
queue.async {
person.firstName = "Rachel"
Thread.sleep(forTimeInterval: 1)
person.lastName = "Moore"
print("2: \(person)")
}
Run Code Online (Sandbox Code Playgroud)
在这个例子中,第一个印刷语句将有效地说"Rachel Ryan",既不是"Rob Ryan"也不是"Rachel Moore".简而言之,我们正在检查我们Person处于内部不一致的状态.
但是,我们再次使用捕获列表来享受价值语义:
queue.async { [person] in
Thread.sleep(forTimeInterval: 0.5)
print("1: \(person)")
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,它会说"Rob Ryan",不知道原件Person可能正处于被另一个线程变异的过程中.(显然,真正的问题不仅仅是通过在第一次async调用中使用值语义来修复,而是在async那里同步第二个调用和/或使用值语义.)