gpt4 book ai didi

ios - Swift - 设置变量属性并发或多线程安全吗?

转载 作者:行者123 更新时间:2023-12-01 19:20:48 26 4
gpt4 key购买 nike

据我所知,在使用并发或多线程时设置变量属性并不安全,但我无法使用以下代码产生崩溃。

class Node {
var data = 0
}

var node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)

for i in 0...1000 {
concurrentQueue.async {
node.data = i // Should get crash at this line
}
}

更新1

感谢@MartinR 在评论中指出。

Enable the “Thread Sanitizer” and it'll report an error immediately.

更新2

如果将data更改为引用类型,代码会发生EXC_BAD_ACCESS KERN_INVALID_ADDRESS崩溃。它并不总是发生,但有时会发生。例如:

class Data {}

class Node {
var data = Data() // Use reference type instead of value type
}

var node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)

for i in 0...1000 {
concurrentQueue.async {
node.data = Data() // EXC_BAD_ACCESS KERN_INVALID_ADDRESS
}
}

这种行为在 Objective-C 中也会发生。同时设置对象属性会导致崩溃。但对于原始类型,崩溃就不会发生。

问题

  • 同时设置值类型属性会导致崩溃吗?
  • 如果不会产生崩溃,设置值类型属性和设置引用类型属性有什么区别?

如果有人也能解释为什么同时设置引用类型属性会产生崩溃,那就完美了。

最佳答案

首先

类值存储在堆内存中,而结构/枚举值存储在堆栈内存中,编译器将尝试在编译时分配这些内存(根据我的第一个引用和许多在线答案)。您可以使用以下代码进行检查:

class MemTest {}
class Node {
var data = MemTest()
}
let node = Node()
let concurrentQueue = DispatchQueue(label: "queue", attributes: .concurrent)

for index in 0...100000 {
concurrentQueue.async {
node.data = MemTest()
withUnsafePointer(to: &node.data) {
print("Node data @ \($0)")
}
withUnsafePointer(to: node.data) {
print("Node data value no. \(index) @ \($0)")
}
}

如何:运行 2 次并检查内存地址是否有值更改时间 500,在类和结构之间切换 MemTest 将显示差异。结构每次都会显示相同的地址,而类会显示不同的地址。所以改变值类型就像改变地址一样,但是内存块不会被恢复,而改变引用类型不仅仅是改变地址,还会恢复并分配新的内存块,这会导致程序崩溃。

其次

但是如果使用withUnsafePointer运行@trungduc的第一个代码块,它会告诉我们for循环的索引var是在运行中分配的并且在堆内存中,那么这是为什么呢?如前所述,如果值类型在编译时可计算,编译器只会尝试分配内存。如果它们不可计算,则该值将被分配在堆内存中并保留在那里直到范围结束(根据我的第二个引用)。所以这里的解释可能是系统将在一切完成后恢复分配的堆栈 - 范围结束(我不太确定)。因此,在这种情况下,代码不会产生我们所知的崩溃。我的结论是,在这种情况下,引用类型变量的内存将被无限制地分配和恢复,而值类型变量的内存只有在系统进入和退出包含该变量的作用域后才会被分配和删除

谢谢

一些话

我的答案不是很扎实,可能有很多语法和拼写错误。所有更新均表示赞赏。提前致谢

更新

对于我不太确定的部分:变量 index 已使用运算符 = 复制到新的内存地址,因此作用域结束位置并不重要,堆栈将在 for 循环后释放经过一番挖掘,在@trungduc的代码中,使用引用类型变量,它将做3件事:

  1. 为类Data分配新内存
  2. 减少对 node.data 中存储的旧 Data 的引用,甚至在不再引用旧 Data 时将其释放
  3. node.data指向新的Data

对于值类型它只会做一件事:

  1. node.data指向堆栈内存中的Integer主要区别在于第 2 步,旧的数据内存有可能被恢复引用类型有可能发生这种情况
________Task 1________|________Task 2________
Allocate new Data #1 |
|Allocate new Data #2
Load pointer to old |
Data |
Reduce reference count|
to old Data |
|Load pointer to old
|Data
Free old Data |
|Reduce reference count
|to old Data (!)
|Free old Data (!)
Reference new Data #1 |
|Reference new Data #2

当使用值类型时,会发生这种情况

________Task 1________|________Task 2________
Reference to Integer 1|
|Reference to Integer 2

在第一种情况下,我们将有各种替代方案,但在大多数情况下,我们会遇到段错误,因为线程 2 在线程 1 释放该指针后尝试取消引用该指针。正如我们所注意到的,可能还有其他问题,例如内存泄漏,线程 2 可能无法正确减少对数据 #1 的引用计数而在第二种情况下,它只是改变指针。

注意

第二种情况,在Intel CPU上永远不会导致崩溃,但在其他CPU上也不能保证,因为许多CPU不 promise 这样做不会导致崩溃。

关于ios - Swift - 设置变量属性并发或多线程安全吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59559738/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com