一尘不染

Apple对具有多个线程的引用和值类型的描述

swift

我正在阅读Apple的文档。我以为我知道何时选择值类型和何时选择引用类型,但是我回到了Swif101。该文档说:

  • 值类型: 数据将在多个线程的代码中使用。
  • 参考类型: 您要创建共享的可变状态

引用类型也跨多个线程共享吗?这两行有什么区别?


阅读 289

收藏
2020-07-07

共1个答案

一尘不染

正如其他人指出的那样,引用类型始终将指向对象的指针传递给对象,这是您想要“共享的可变状态”的理想选择(正如您所引用的文档所述)。但是,很显然,如果要在多个线程之间进行突变/访问引用类型,请确保同步对它的访问(通过专用的串行队列,读写器模式,锁等)。

但是,值类型稍微复杂一些。是的,正如其他人指出的那样,如果将值类型作为参数传递给在另一个线程上执行某操作的方法,则实际上是在使用该值类型的副本(乔什关于尽管写)。这样可以确保传递给该方法的对象的完整性。很好(并且这里的其他答案已充分覆盖了该问题)。

但是当您处理闭包时,它变得更加复杂。例如,考虑以下内容:

struct Person {
    var firstName: String
    var lastName: String
}

var person = Person(firstName: "Rob", lastName: "Ryan")

DispatchQueue.global().async {
    Thread.sleep(forTimeInterval: 1)
    print("1: \(person)")
}

person.firstName = "Rachel"
Thread.sleep(forTimeInterval: 2)
person.lastName = "Moore"
print("2: \(person)")

显然,您通常不sleep会这样做,但我这样做是为了说明这一点:即,即使我们正在处理值类型和多个线程,person闭包中的引用也与您处理的实例
相同 在主线程(或运行该线程的任何线程)上,而不是其副本。如果要处理可变对象,则不是线程安全的。

我设计了这个示例来说明这一点,在print上面的闭包中的语句将报告“ Rachel
Ryan”,有效地显示了处于Person不一致状态的值类型的状态。

对于使用值类型的闭包,如果您想享受值语义,则必须更改该async调用以使用单独的变量:

let separatePerson = person
queue.async {
    Thread.sleep(forTimeInterval: 1)
    print("1: \(separatePerson)")
}

或者,甚至更简单,使用“捕获列表”,它指示闭包应捕获哪些值类型变量:

queue.async { [person] in
    Thread.sleep(forTimeInterval: 1)
    print("1: \(person)")
}

使用以上两个示例中的任何一个,您现在都在享受值语义,复制对象,并且print即使原始person对象在另一个线程上发生了变异,该语句也将正确报告“
Rob Ryan” 。

因此,如果要处理值类型和闭包,则可以在线程之间共享值类型,除非您显式使用捕获列表(或等效的东西)以享受值语义(即根据需要复制对象)。

2020-07-07