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

时间:2016-08-11 18:09:37

标签: swift multithreading class struct

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

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

多个线程之间是否也共享引用类型?这两条线有什么区别?

5 个答案:

答案 0 :(得分:4)

是的,如果多个线程使用引用共享;这正是问题所在。所有线程引用到内存中的相同实际数据。然后他们需要同步机制来确保单独的线程'读写不冲突。这些机制在代码复杂性和性能方面都有代价。

不共享值类型的实例:每个线程都有自己的副本。*这意味着每个线程都可以读取和写入其实例,而不必担心其他线程在做什么。

*使用Swift stdlib类型的标准copy-on-write异常:只有在数据发生变异时才会执行实际的副本。

答案 1 :(得分:3)

令人困惑的措辞。

  

值类型:数据将用于跨多个线程的代码中。

通过这种方式,我相信当您希望许多线程从您的数据中读取时,它们意味着它很有用。这是因为您知道,无论何时向新线程提供数据副本,您都不会使任何其他副本受到其他线程意外突变的风险。

在这样的上下文中使用值类型,您不需要共享可变状态,允许您避免来自处理引用的许多类错误(竞争条件,死锁,活锁等)类型。

  

参考类型:您想要创建共享的可变状态

只有两个线程各自保留对共享实例的引用,才能在线程之间共享引用类型。

答案 2 :(得分:3)

正如其他人所指出的那样,引用类型总是传递一个指向对象的指针,这是你想要一个共享的,可变的状态的理想选择。 (正如你引用的那份文件所说)。显然,如果您在多个线程中改变/访问引用类型,请确保同步对它的访问(通过专用串行队列,读写器模式,锁等)。

但值类型稍微复杂一些。是的,正如其他人所指出的那样,如果您将值类型作为参数传递给某个方法然后在另一个线程上执行某些操作,那么您基本上使用该值类型的副本(Josh的注释)关于写时复制,尽管如此)。这可确保传递给方法的对象的完整性。这很好(这里已经充分涵盖了其他答案)。

但是当你处理闭包时它会变得更复杂。例如,考虑以下因素:

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",有效地显示{{1}的状态值类型处于不一致状态。

对于使用值类型的闭包,如果要享受值语义,则必须更改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)") } 语句将正确报告" Rob Ryan"即使原始print对象在另一个线程上发生变异。

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

答案 3 :(得分:1)

Apple建议在跨多个线程使用数据时使用值类型,以避免使用共享实例,而不是使用它。

当您为线程提供值类型的对象时,每个线程都会获得自己的副本,这反过来又让您不必担心同步。

通常,您应该更喜欢使您的值类型不可变(尽管Swift提供了一种编码变异函数的机制)。执行此操作时,即使使用引用类型,也不会出现同步问题。

答案 4 :(得分:0)

  

值类型:数据将用于跨多个线程的代码中。

分配后,数据不会在线程间发生变化。不用担心其他线程,因为每个线程都有自己的副本。

  

参考类型:您想要创建共享的可变状态

数据可能会因为其类型引用(即指向内存中的相同数据)而影响其他线程的任何线程都会发生变化。