Swift Array copy 的線程安全問題


Swift Array copy 的線程安全問題

NSArray 繼承自 NSObject,屬於對象,有 copy 方法。Swift 的 Array 是 struct,沒有 copy 方法。把一個 Array 變量賦值給另一個變量,兩個變量的內存地址相同嗎?與此相關的有多線程安全問題。本文探究這兩個問題。

內存地址

定義測試 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)
}

測試 Int array

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()
	}
}

結果

一個 object array 變量賦值給另一個變量,兩個變量的內存地址相同,也就是說沒有 copy。原來的 array 改變后,內存地址改變,但不影響被賦值的變量。

線程安全問題

以上的寫法是不會報錯的。如果把 array 的賦值寫入 async closure,就會報錯。多試幾次,會有不同的錯誤。

Int array 的錯誤

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 變量時才 copy。Array 的 copy 是線程不安全的。

轉載請注明出處:http://www.cnblogs.com/silence-cnblogs/p/6288208.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM