默认情况下,数组按值传递&线程安全

时间:2017-08-01 12:03:48

标签: swift multithreading swift3 concurrency thread-safety

假设我有一个具有Array对象Photo的课程:

class PhotoManager {
  fileprivate var _photos: [Photo] = []

  var photos: [Photo] {
      return _photos
  }
}

我读了one article,其中说明了以下内容:

  

默认情况下,Swift类实例通过引用传递   结构通过值传递。 Swift的内置数据类型,如Array和   字典,作为结构实现。

意味着上面的getter返回[Photo]数组的副本。

然后,同一篇文章试图通过将代码重构为:

来使getter线程安全
fileprivate let concurrentPhotoQueue = DispatchQueue(label: "com.raywenderlich.GooglyPuff.photoQueue",
                                                       attributes: .concurrent)
  fileprivate var _photos: [Photo] = []
  var photos: [Photo] {
    var photosCopy: [Photo]!
    concurrentPhotoQueue.sync {
        photosCopy = self._photos
    }
    return photosCopy
  }

上述代码明确地在getter中复制了self._photos

我的问题是:

  1. 如果默认情况下swift已经返回了一个副本(按值传递),就像文章首先说的那样,为什么文章会再次复制到photosCopy以使其成为线程安全的?我觉得自己并不完全理解那篇文章中提到的这两部分。

  2. 对于Array实例,Swift3是否真的按值传递,如文章所述?

  3. 有人可以帮我澄清一下吗?谢谢!

2 个答案:

答案 0 :(得分:0)

1)在代码示例中,您提供的内容将是_photos的返回副本。 如文章所述:

The getter for this property is termed a read method as it’s reading
the mutable array. The caller gets a copy of the array and is protected 
against mutating the original array inappropriately.

这意味着您可以从课堂外访问_photos,但您无法从那里更改它们。 photos的值只能在类内部进行更改,以防止此数组意外更改。

2)是的,Array是一个值类型结构,它将按值传递。您可以在Playground中轻松查看

let arrayA = [1, 2, 3]
var arrayB = arrayA

arrayB[1] = 4 //change second value of arrayB

print(arrayA) //but arrayA didn't change

UPD#1

在文章中,他们有方法func addPhoto(_ photo: Photo)_photos数组添加新照片的原因是什么使得访问此属性不是线程安全的。这意味着_photos的价值可以在几个线程中同时改变,这将导致问题。

他们通过在concurrentQueue上用.barrier写照片使其线程安全,_photos数组每次更改一次

来修复它
func addPhoto(_ photo: Photo) {
  concurrentPhotoQueue.async(flags: .barrier) { // 1
    self._photos.append(photo) // 2
    DispatchQueue.main.async { // 3
      self.postContentAddedNotification()
    }
  }
} 

现在为了确保线程安全,您需要在同一队列中读取_photos数组。这是他们重构读取方法

的唯一原因

答案 1 :(得分:0)

我会反过来解答你的问题:

  
      
  1. 对于Array实例,Swift3是否真的按值传递值,如文章所述?
  2.   

简单回答:是的

但我猜这不是你在关注“Swift3 真的通过值传递”时所关注的问题。 Swift的行为好像数组完全被复制,但在幕后它优化了操作,整个数组不会被复制,直到它需要。 Swift使用称为写时复制(COW)的优化。

然而对于Swift程序员如何复制完成并不像操作的语义那么重要 - 这是在赋值/复制之后两个数组是独立的和变异的不影响另一个。

  
      
  1. 如果默认情况下swift已经返回了一个副本(按值传递),就像文章首先说的那样,为什么文章会再次复制到photosCopy以使其成为线程安全的?我觉得自己并不完全理解那篇文章中提到的这两部分。
  2.   

此代码正在做的是确保副本以线程安全的方式完成。

数组不是一个微不足道的值,它实现为多字段结构,其中一些字段引用其他结构和/或对象 - 这是支持数组扩展大小等功能所必需的。

在多线程系统中,一个线程可能会尝试复制该阵列,而另一个线程正在尝试更改该阵列。如果允许这些同时发生,则事情很容易出错,例如,复制过程中阵列可能会发生变化,导致副本无效 - 部分旧值,部分新值。

Swift 本身 线程安全;特别是它不会阻止在执行复制时更改数组。您使用GCD队列解决此问题的代码,以便在通过一个线程对阵列进行任何更改期间,阻止对任何其他线程中的阵列的所有其他写入或读取,直到更改完成。

您可能还担心他们会在这里发送多份副本,self._photosphotoCopy,然后photoCopy到返回值。虽然语义上这是实践中发生的事情,但可能只有一个复杂的副本(这将是线程安全的),因为Swift系统将进行优化。

HTH