在多线程环境中写入时的 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 数组复制的主要内容,如果未能解决你的问题,请参考以下文章

在多线程环境中使用 std::string 时 Clang 的线程清理器警告

Swift 多线程环境中的 Copy-on-Write

如果我在多线程服务器程序中运行并行代码会发生啥? [复制]

HashMap多线程下不安全的具体体现

如何使用 Java 在多线程环境中测试某些东西 [重复]

有没有办法在多线程应用程序中安全地使用 errno? [复制]