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 {}
出力メモリアドレスを定義するclosurelet 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はスレッドが安全ではありません.
class MyClass {
var intArr = [Int]() var structArr = [MyStructElement]() var objectArr = [MyClassElement]()
}struct MyStructElement {}class MyClassElement {}
let memoryAddress: (Any) -> String = { guard let cVarArg = $0 as? CVarArg else { return "Can not find memory address" } return String(format: "%p", cVarArg)
}
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}
override func viewDidLoad() { super.viewDidLoad()
for _ in 0...1000 {
testIntArr()
}
}
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}
override func viewDidLoad() { super.viewDidLoad()
for _ in 0...1000 {
testStructArr()
}
}
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}
override func viewDidLoad() { super.viewDidLoad()
for _ in 0...1000 {
testObjectArr()
}
}
DispatchQueue.global().async { let arr = my.intArr //
var sum = 0
for i in arr {
sum += i
} print("Sum:", sum)
}
DispatchQueue.global().async { let arr = my.structArr //
var sum = 0
for _ in arr {
sum += 1
} print("Sum:", sum)
}
DispatchQueue.global().async { let arr = my.objectArr //
var sum = 0
for _ in arr {
sum += 1
} print("Sum:", sum)
}