简体   繁体   English

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

[英]Swift - Is setting variable property concurrency or multithreading safe?

As I know setting a variable property when using concurrency or multithreading is not safe but I can't produce a crash with below code.据我所知,在使用并发或多线程时设置变量属性并不安全,但我无法使用以下代码产生崩溃。

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

UPDATE1更新1

Thanks @MartinR for pointing out in his comment.感谢@MartinR 在评论中指出。

Enable the “Thread Sanitizer” and it'll report an error immediately.启用“Thread Sanitizer”,它会立即报告错误。

UPDATE2更新2

The code got EXC_BAD_ACCESS KERN_INVALID_ADDRESS crash if changing data to reference type.如果将data更改为引用类型,代码EXC_BAD_ACCESS KERN_INVALID_ADDRESS发生EXC_BAD_ACCESS KERN_INVALID_ADDRESS崩溃。 It doesn't always happen but sometimes it will.它并不总是发生,但有时会发生。 For example:例如:

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

This behavior also happens in Objective-C.这种行为也发生在 Objective-C 中。 Setting object property concurrently will cause crash.同时设置对象属性会导致崩溃。 But with primitive type, the crash will not happen.但是对于原始类型,崩溃不会发生。

Questions问题

  • Does setting value type property concurrently will produce a crash?同时设置值类型属性会导致崩溃吗?
  • If it doesn't produce a crash, what is the difference between setting value type property and setting reference type property?如果不产生崩溃,设置值类型属性和设置引用类型属性有什么区别?

It's perfect if anyone can also explain why setting reference type property concurrently will produce a crash.如果有人也能解释为什么同时设置引用类型属性会导致崩溃,那就太完美了。

First off首先

Class value are stored in heap memory while struct/enum value are stored in stack memory and compiler will try to allocate those memory at compile time (according to my first ref and many online answer).类值存储在堆内存中,而结构/枚举值存储在堆栈内存中,编译器将尝试在编译时分配这些内存(根据我的第一个参考和许多在线答案)。 You can check using this code:您可以使用以下代码进行检查:

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

How to: run 2 time and check memory address for value changed time 500, switch MemTest between class and struct will show the difference.如何:运行 2 次并检查内存地址的值更改时间 500,在类和结构之间切换 MemTest 将显示差异。 Struct will show the same while class will show different address between each time.结构将显示相同,而类将在每次之间显示不同的地址。 So changing value type is just like changing address but the the memory blocks will not be restored while changing reference type is not just changing address but also restore and allocate new memory blocks which will cause the program to break.所以改变值类型就像改变地址一样,但不会恢复内存块,而改变引用类型不仅是改变地址,还会恢复和分配新的内存块,这会导致程序中断。

Secondly其次

But if running first code block of @trungduc with withUnsafePointer, it will show us that the index var of for loop is allocated on the go and in the heap memory, so why is that?但是如果用withUnsafePointer运行@trungduc的第一个代码块,它会告诉我们for循环的索引变量是在运行中和堆内存中分配的,那么为什么会这样呢? As mentioned before, compiler will only try to allocate the mem if the value type is calculable at compile time.如前所述,如果值类型在编译时可计算,编译器只会尝试分配内存。 If the they are not calculable, the value will be allocated in heap memory and stay there till the end of scope (according to my second ref).如果它们不可计算,则该值将分配在堆内存中并一直保留到作用域结束(根据我的第二个参考)。 So may be the explanation here is the system will restored the allocated stack after everything is done - end of scope (This I'm not very sure).所以这里的解释可能是系统将在一切完成后恢复分配的堆栈 - 范围结束(我不太确定)。 So in this case the code will not produce a crash as we know.所以在这种情况下,我们知道代码不会产生崩溃。 My conclusion, the reference type variable's mem will be allocated and restored with no restriction in this case whereas value type variable's mem will be allocated and removed only after the system enter and exit a the scope which contains said variable我的结论是,在这种情况下,引用类型变量的 mem 将被无限制地分配和恢复,而值类型变量的 mem 只有在系统进入和退出包含所述变量的作用域后才会分配和删除

Thanks谢谢

Some words一些单词

My answer is not very solid and might have lots of grammar and spelling error.我的回答不是很可靠,可能有很多语法和拼写错误。 All update are appreciated.感谢所有更新。 Thanks in advance提前致谢

Update更新

For the part I'm not very sure: variable index was copied to a new memory address with operator = so it doesn't matter where the scope end, stack will be released after for loop After some digging, in @trungduc's code, with reference type variable, it will do 3 things:对于我不太确定的部分:变量index被复制到一个新的内存地址与操作符=所以范围在哪里结束无关紧要,堆栈将在 for 循环后释放经过一些挖掘,在@trungduc 的代码中,用引用类型变量,它会做三件事:

  1. Allocate new memory for class Data为类Data分配新内存
  2. Reduce reference to old Data stored in node.data , even free old Data if it's no longer referenced减少参照旧Data存储在node.data ,甚至是免费的旧Data ,如果它不再引用
  3. Point node.data to new Datanode.data指向新Data

While for value type it will do 1 thing only:而对于值类型,它只会做一件事:

  1. Point node.data to Integer in stack memory The major difference is in step 2 where there is a chance the old Data memory is restored There are possibilities where this scenario will happen with reference typenode.data指向堆栈内存中的Integer主要区别在于第 2 步,其中有可能恢复旧的Data内存 引用类型可能会发生这种情况
________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

while with value type, this will happen而对于值类型,这会发生

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

In the first case we will have various alternative scenarios but in most case, we get a segmentation fault because thread 2 tries to dereference that pointer after thread 1 free it.在第一种情况下,我们将有各种替代方案,但在大多数情况下,我们会遇到分段错误,因为线程 2 试图在线程 1 释放该指针后取消引用该指针。 There might be other issues like memory leaking as we notice, thread 2 might not reduce reference count to Data #1 correctly Whereas in second case, it's just changing the pointer.正如我们所注意到的,可能还有其他问题,例如内存泄漏,线程 2 可能无法正确减少对数据 #1 的引用计数,而在第二种情况下,它只是更改了指针。

Note笔记

In second case, it will never cause crash on Intel CPU but not guaranteed on other CPUs as well because many CPUs do not promise that doing this will not cause a crash.在第二种情况下,它永远不会在 Intel CPU 上导致崩溃,但在其他 CPU 上也不能保证,因为许多 CPU 不承诺这样做不会导致崩溃。

Non-atomic doesn't mean that app will crash if multiple threads are using the shared resource.非原子并不意味着如果多个线程正在使用共享资源,应用程序将崩溃。

Atomic Properties原子性质

Defining a property as atomic will guarantee that a valid value will be returned.将属性定义为原子将保证将返回有效值。 Notice that valid does not always mean correct.请注意,有效并不总是意味着正确。

This also does not mean that atomic properties are thread safe.这也不意味着原子属性是线程安全的。 Different threads can attempt to write and read a the same time.不同的线程可以尝试同时写入和读取。 One of two values will be returned — the value before the change or the value of the change将返回两个值之一 - 更改前的值或更改的值

Non-Atomic Properties非原子属性

Non atomic properties has no guarantee regarding the returned value.非原子属性不能保证返回值。 It can be the correct value, a partially written value or even some garbage value.它可以是正确的值、部分写入的值甚至是一些垃圾值。

It simply means that the final value will not be consistent.它只是意味着最终值将不一致。 You won't know which thread will update the value last.您将不知道哪个线程将最后更新该值。

You can refer the link for more clarification on this.您可以参考链接以获取更多说明。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM