Swift 什么状况会发生内存访问冲突

众所周知,Swift 是一门类型安全的语言,它会经过编译器报错来阻止你代码中不安全的行为。好比变量必须在使用以前声明、变量被销毁以后内存不能在访问、数组越界等问题。数组

Swift 会经过对于修改同一块内存,同一时间以互斥访问权限的方式(同一时间,只能有一个写权限),来确保你的代码不会发生内存访问冲突。虽然 Swift 是自动管理内存的,在大多数状况下你并不须要关心这个。但理解何种状况下会发生内存访问冲突也是十分必要的。安全

首先,来看一下什么是内存访问冲突。markdown

内存访问冲突

当你设值或者读取变量的值得时候,就会访问内存。app

var age = 10 // 写权限
print(age) // 读权限
复制代码

当咱们对同一块内存,同时进行读写操做时,会产生不可预知的错误。好比上面的 age,假如在你读取它值的期间有别的代码将它设为 20,那么你读取到的有多是 10,也有多是 20。这就产生了问题。async

内存访问冲突:对同一块内存,同时进行读写操做,或者同时进行多个写入操做时,就会形成内存访问冲突。ide

了解了什么是内存访问冲突,下面来看下什么状况下回形成内存访问冲突。函数

In-Out 参数

当 In-Out 参数为全局变量,而且该变量在函数体内被修改时,就会形成内存访问冲突。好比下面的代码:ui

var age = 10

func increment(_ num: inout Int) { // step1
    num += age // step2
}
increment(&age)
复制代码

increment(:) 在整个函数体内,对全部的 In-Out 参数都有写权限。在上述代码中,step1 已经得到了 age 的写权限,而 step2 有获得了 age 的读权限,这样就形成了同一块内存,同时进行了读写操做。从而形成了内存访问冲突。spa

上面的问题能够经过将 age 拷贝一份来解决:线程

// step1
var copyOfAge = age
increment(&copyOfAge)
age = copyOfAge
复制代码

step1 将 age 的值拷贝到另外一块内存上,这样在函数体内就是存在对 age 的读权限和对 copyOfAge 的写权限,由于 age 和 copyOfAge 是两块内存,因此就不会形成内存访问冲突。

结构体的 mutating 函数

对于结构体的 mutating 函数来讲,它整个函数体都有 self 的写权限。

struct Person {
    var age: Int
    mutating func increment(_ num: inout Int) { 
        age += num 
    }
}

var p1 = Person(age: 10)
p1.increment(&p1.age)
复制代码

上述的代码编译器会报错:Overlapping accesses to 'p1', but modification requires exclusive access; consider copying to a local variable。很明显这是一个内存访问冲突。

In-Out 参数得到了 p1 的写权限;mutating 函数也得到了 p1 的写权限。同一块内存,同时有两个写操做。形成内存访问冲突。能够经过同上的拷贝操做来解决。

值类型的属性

对于结构体、枚举、元祖等值类型来讲,修改它们的属性就至关于修改它们整个的值。好比下面的代码:

func increment(_ num1: inout Int, _ num2: inout Int) {
    print(num1 + num2)
}

var tuple = (age: 10, height: 20)
increment(&tuple.age, &tuple.height)
复制代码

&tuple.age 拿到了 tuple 的写权限,&tuple.height 又拿了 tuple 的写权限。同一块内存,同时有两个写操做。形成内存访问冲突。

这个问题能够经过局部变量来解决:

func someFunction() {
    var tuple = (age: 10, height: 20)
    increment(&tuple.age, &tuple.height)
}
复制代码

由于在 someFunction() 函数里,age 和 height 没有产生任何的交互(没有在其期间去读取或者写入 age 和 height),因此编译器能够保证内存安全。

PS:关于评论区的问题,在 someFunction() 函数里没有任何交互是什么意思?

答:在someFunction() 里,编译器能够保证没有别的线程来读取或者修改 tuple。所以,能够保证内存安全。而对于全局变量,编译器没法保证是否有别的线程在读取或者修改。

下面的代码就是在函数体内有交互的代码,虽然是局部变量,但涉及多个线程修改 tuple 的值,所以会形成内存访问冲突:

func someFunction() {
    var tuple = (age: 10, height: 20)
    
    DispatchQueue.main.async {
        tuple.age += 10
    }
    
    DispatchQueue.main.async {
        increment(&tuple.age, &tuple.height)
    }
}
复制代码

总结

  • 对同一块内存,同时进行读写操做,或者同时进行多个写入操做时,就会形成内存访问冲突。
  • 会形成内存访问冲突的状况:
    • In-Out 为全局参数,而且在函数体内修改了它。
    • 结构体的 mutating 函数内修改结构体的值。
    • 同一值类型的多个属性当作函数的 In-Out 参数。
相关文章
相关标签/搜索