Swift Array copy 的线程安全问题
NSArray 继承自 NSObject,属于对象,有 copy 方法。Swift 的 Array 是 struct,没有 copy 方法。把一个 Array 变量赋值给另一个变量,两个变量的内存地址相同吗?与此相关的有多线程安全问题。本文探究这两个问题。
内存地址定义测试 class 和 struct
classMyClass{varintArr=[Int]()varstructArr=[MyStructElement]()varobjectArr=[MyClassElement]()}structMyStructElement{}classMyClassElement{}
定义输出内存地址的 closure
letmemoryAddress:(Any)->String={guardletcVarArg=$0as?CVarArgelse{return"Cannotfindmemoryaddress"}returnString(format:"%p",cVarArg)}测试 Int array
privatefunctestIntArr(){print(#function)letmy=MyClass()foriin0...10000{my.intArr.append(i)}print("Beforearraddress:",memoryAddress(my.intArr))//CopyArrayisNOTthreadsafeletarr=my.intArr//Ifmovethisintoasyncclosure,crashprint("Temparraddress:",memoryAddress(arr))//Copy.Addressdifferentfrommy.intArrDispatchQueue.global().async{varsum=0foriinarr{sum+=i}print("Sum:",sum)//0+1+...+10000=50005000}my.intArr.removeAll()for_in0...10000{my.intArr.append(0)}print("Afterarraddress:",memoryAddress(my.intArr))//Newaddress}
在 view controller 中进行测试
overridefuncviewDidLoad(){super.viewDidLoad()for_in0...1000{testIntArr()}}
结果
Int array 的内存地址不同,赋值过程发生了 copy。
测试 struct arrayprivatefunctestStructArr(){print(#function)letmy=MyClass()for_in0...10000{my.structArr.append(MyStructElement())}print("Beforearraddress:",memoryAddress(my.structArr))//CopyArrayisNOTthreadsafeletarr=my.structArr//Ifmovethisintoasyncclosure,crashprint("Temparraddress:",memoryAddress(arr))//Copy.Addressdifferentfrommy.structArrDispatchQueue.global().async{varsum=0for_inarr{sum+=1}print("Sum:",sum)//10001}my.structArr.removeAll()for_in0...10000{my.structArr.append(MyStructElement())}print("Afterarraddress:",memoryAddress(my.structArr))//Newaddress}
在 view controller 中进行测试
overridefuncviewDidLoad(){super.viewDidLoad()for_in0...1000{testStructArr()}}
结果
Struct array 的内存地址不同,赋值过程发生了 copy。
测试 Object arrayprivatefunctestObjectArr(){print(#function)letmy=MyClass()for_in0...10000{my.objectArr.append(MyClassElement())}print("Beforearraddress:",memoryAddress(my.objectArr))//CopyArrayisNOTthreadsafeletarr=my.objectArr//Ifmovethisintoasyncclosure,crashprint("Temparraddress:",memoryAddress(arr))//Notcopy.Sameasmy.objectArrDispatchQueue.global().async{varsum=0for_inarr{sum+=1}print("Sum:",sum)//10001}my.objectArr.removeAll()for_in0...10000{my.objectArr.append(MyClassElement())}print("Afterarraddress:",memoryAddress(my.objectArr))//Newaddress}
在 view controller 中进行测试
overridefuncviewDidLoad(){super.viewDidLoad()for_in0...1000{testObjectArr()}}
结果
一个 object array 变量赋值给另一个变量,两个变量的内存地址相同,也就是说没有 copy。原来的 array 改变后,内存地址改变,但不影响被赋值的变量。
线程安全问题以上的写法是不会报错的。如果把 array 的赋值写入 async closure,就会报错。多试几次,会有不同的错误。
Int array 的错误DispatchQueue.global().async{letarr=my.intArr//在这里赋值会报错varsum=0foriinarr{sum+=i}print("Sum:",sum)}
Struct array 的错误DispatchQueue.global().async{letarr=my.structArr//在这里赋值会报错varsum=0for_inarr{sum+=1}print("Sum:",sum)}
Object array 的错误DispatchQueue.global().async{letarr=my.objectArr//在这里赋值会报错varsum=0for_inarr{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 是线程不安全的。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。