zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    NAND FLASH扇区管理
    ECC内存校验算法
    实时数据库简介
    windows标准控件
    PLC一些资料
    at命令
    Vi 常用命令列表
    PHP继承及实现
    Mongodb php扩展及安装
    Linux下jdk1.6安装指引
  • 原文地址:https://www.cnblogs.com/silence-cnblogs/p/6288208.html
Copyright © 2011-2022 走看看