在多线程环境中写入时的 Swift 数组复制
Posted
技术标签:
【中文标题】在多线程环境中写入时的 Swift 数组复制【英文标题】:Swift array copy on write in a multithreaded environment 【发布时间】:2016-11-10 11:33:27 【问题描述】: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]
当线程 B 尝试访问其元素时,原始数字数组 ([1, 2, 3]
) 是否会被释放,从而导致 incorrect checksum for freed object - object was probably modified after being freed
?
【问题讨论】:
您收到哪个对象的错误/警告? 参考这个:***.com/questions/19840671/… 我非常不喜欢 Swift 数组通过值而不是指针传递的方式。这已经让我晾干了好几次了。 【参考方案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 中的结构如何充当值类型。重新分配 foo.numbers
时不会覆盖原始的 [1, 2, 3]
数组。
示例
假设你有 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]
。这就是写时复制对结构的意义——只有当您修改结构并且其他人有旧副本时,复制的性能才会受到影响。
这 10 个线程中的每一个都像拥有自己的 [1, 2, 3]
副本一样,这就是值类型的含义。
【讨论】:
【参考方案2】:首先,考虑一下没有并发会发生什么:
在所有强引用被删除之前,原始数组不会被释放,所以只要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
【讨论】:
以上是关于在多线程环境中写入时的 Swift 数组复制的主要内容,如果未能解决你的问题,请参考以下文章