Swift Array copyのスレッドセキュリティの問題



Swift Array copyのスレッドセキュリティの問題


NSArrayはNSObjectから継承され、オブジェクトに属し、copyメソッドがあります.SwiftのArrayはstructでcopyメソッドはありません.1つのArray変数を別の変数に割り当てます.2つの変数のメモリアドレスは同じですか?これに関連するマルチスレッドセキュリティの問題.本文はこの二つの問題を探究する.

メモリアドレス


テストclassとstructの定義
class MyClass {    
    var intArr = [Int]()    var structArr = [MyStructElement]()    var objectArr = [MyClassElement]()
}struct MyStructElement {}class MyClassElement {}

出力メモリアドレスを定義するclosure
let memoryAddress: (Any) -> String = {    guard let cVarArg = $0 as? CVarArg else { return "Can not find memory address" }    return String(format: "%p", cVarArg)
}

Intarrayのテスト

private func testIntArr() {    print(#function)    
    let my = MyClass()    for i in 0...10000 {
        my.intArr.append(i)
    }    print("Before arr address:", memoryAddress(my.intArr))        
    // Copy Array is NOT thread safe
    let arr = my.intArr // If move this into async closure, crash
    print("Temp   arr address:", memoryAddress(arr)) // Copy. Address different from my.intArr
    DispatchQueue.global().async {        var sum = 0
        for i in arr {
            sum += i
        }        print("Sum:", sum) // 0 + 1 + ... + 10000 = 50005000
    }
    
    my.intArr.removeAll()    for _ in 0...10000 {
        my.intArr.append(0)
    }    print("After  arr address:", memoryAddress(my.intArr)) // New address}

view controllerでテスト
override func viewDidLoad() {    super.viewDidLoad()    
    for _ in 0...1000 {
        testIntArr()
    }
}

結果
Int arrayのメモリアドレスが異なり、付与プロセスでcopyが発生しました.

テストstruct array

private func testStructArr() {    print(#function)    
    let my = MyClass()    for _ in 0...10000 {
        my.structArr.append(MyStructElement())
    }    print("Before arr address:", memoryAddress(my.structArr))    
    // Copy Array is NOT thread safe
    let arr = my.structArr // If move this into async closure, crash
    print("Temp   arr address:", memoryAddress(arr)) // Copy. Address different from my.structArr
    DispatchQueue.global().async {        var sum = 0
        for _ in arr {
            sum += 1
        }        print("Sum:", sum) // 10001
    }
        
    my.structArr.removeAll()    for _ in 0...10000 {
        my.structArr.append(MyStructElement())
    }    print("After  arr address:", memoryAddress(my.structArr)) // New address}

view controllerでテスト
override func viewDidLoad() {    super.viewDidLoad()    
    for _ in 0...1000 {
        testStructArr()
    }
}

結果
Struct arrayのメモリアドレスが異なり、付与プロセスでcopyが発生します.

Object arrayのテスト

private func testObjectArr() {    print(#function)    
    let my = MyClass()    for _ in 0...10000 {
        my.objectArr.append(MyClassElement())
    }    print("Before arr address:", memoryAddress(my.objectArr))    
    // Copy Array is NOT thread safe
    let arr = my.objectArr // If move this into async closure, crash
    print("Temp   arr address:", memoryAddress(arr)) // Not copy. Same as my.objectArr
    DispatchQueue.global().async {        var sum = 0
        for _ in arr {
            sum += 1
        }        print("Sum:", sum) // 10001
    }
        
    my.objectArr.removeAll()    for _ in 0...10000 {
        my.objectArr.append(MyClassElement())
    }    print("After  arr address:", memoryAddress(my.objectArr)) // New address}

view controllerでテスト
override func viewDidLoad() {    super.viewDidLoad()    
    for _ in 0...1000 {
        testObjectArr()
    }
}

結果
1つのobject array変数は、2つの変数のメモリアドレスが同じ、すなわちcopyがない別の変数に値を割り当てます.元のarrayが変更されると、メモリアドレスは変更されますが、割り当てられた変数には影響しません.

スレッドセキュリティの問題


以上の書き方は間違いを報告しません.arrayの付与値をasync closureに書き込むとエラーが発生します.何度もやってみると、違う間違いがあります.

Intarrayのエラー

DispatchQueue.global().async {    let arr = my.intArr //  
    var sum = 0
    for i in arr {
        sum += i
    }    print("Sum:", sum)
}

Struct arrayのエラー

DispatchQueue.global().async {    let arr = my.structArr //  
    var sum = 0
    for _ in arr {
        sum += 1
    }    print("Sum:", sum)
}

Object arrayのエラー

DispatchQueue.global().async {    let arr = my.objectArr //  
    var sum = 0
    for _ in arr {
        sum += 1
    }    print("Sum:", sum)
}

Int arrayとstruct arrayでは、付与時にcopyが行われていますが、この手順は原子操作ではないはずなのでasync closureを入れるとエラーが発生します.object arrayの場合、付与プロセスはcopyを行わないが、元のarrayを変更し、付与されたオブジェクトを一定に保つには、copyも行わなければならない.すなわちarrayを更新するときにcopyを行う.このときのcopyも原子操作ではないと推測されるので、async closureを入れるとエラーになります.
Arrayの付与プロセスがcopyを行うかどうかは、その要素タイプに関係しています.arrayの要素がInt,structなどであれば,付与時にcopyとなる.arrayの要素がobjectの場合、付与時にcopyは使用されず、付与後にarray変数の1つを更新するとcopyになります.Arrayのcopyはスレッドが安全ではありません.