在多线程环境中写入时的Swift数组副本

时间:2016-07-08 01:02:43

标签: ios swift

Swift数组是在写入时复制的值类型。如果原始数组未发生变异,则“复制”指向相同的内存位置。

假设我们有一个由多个线程引用的类

class Foo {
    var numbers: [Int] = [1, 2, 3]
}
let foo = Foo()

如果主题A“复制”numbers

var numbers = foo.numbers

然后后来的线程B用不同的数组实例替换numbers

foo.numbers = [4, 5, 6]

原始数字数组([1, 2, 3])是否会被释放,当线程B尝试访问其元素时会导致incorrect checksum for freed object - object was probably modified after being freed

2 个答案:

答案 0 :(得分:1)

在您修改var numbers = foo.numbers之前,

[1, 2, 3]将始终包含var numbers

let foo = Foo()
// Thread A
var numbers = foo.numbers

// Thread b
foo.numbers = [4, 5, 6]

// Thread A again - 'var numbers' still has a "reference" to [1, 2, 3]
print(numbers) // "[1, 2, 3]"

我认为你误解了swift中的结构如何作为价值类型。重新分配[1, 2, 3]时,原始的foo.numbers数组不会被覆盖。

实施例

假设您有10个线程,其中所有“复制”foo.numbers,此时不会复制数组的内存。

var myTheadNumbers = foo.numbers

让我们说线程1修改数组

myThreadNumbers.append(4)

[1, 2, 3]被复制到一个新数组,然后通过附加4来修改。

线程1现在拥有一个[1, 2, 3, 4]的单个数组,而线程2-10仍然共享原始[1, 2, 3]。这就是用于结构的copy-on-write意味着 - 当你修改一个结构而另一个人有一个旧的副本时,你只会获得复制的性能。

这10个主题中的每一个都有自己的[1, 2, 3]副本,这就是值类型的含义。

答案 1 :(得分:0)

首先,考虑一下没有并发的情况:

在删除所有强引用之前,不会释放原始数组,因此只要numbers仍保留引用,原始数组将在内存中。

将新数组分配给foo.numbers时,删除对原始数组的一个引用,但numbers仍保留引用,因此不会释放它。 foo.numbers现在对新数组有强烈的引用。

即使我修改了foo.numbers

foo.numbers.append(4)

numbers仍将保留对原始数组的引用,foo.numbers现在将保留对新数组的引用,因为它在修改时被复制。

一旦你将并发性引入所有这些,它就会变得混乱,因为这些复制和赋值操作可能不是原子的。任何对象的并发更新都需要保护关键部分以防止损坏。

您可以使用串行调度队列来管理numbers数组的任何更新。

class Foo {

    private var _numbers: [Int] = [1,2,3];

    private let serialQ = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL)

    var numbers: [Int] {
        set {
            dispatch_sync(self.serialQ) { 
                self._numbers = newValue
            }
        }
        get {
            return self._numbers
        }
    }
}