不可变类型的实例本质上是线程安全的

本文关键字:线程 安全 实例 类型 不可变 本质上 | 更新日期: 2023-09-27 17:54:37

我搜索为什么。net字符串是不可变的?得到了这个答案:

不可变类型的实例本质上是线程安全的,因为没有线程可以修改它,线程以某种方式修改它的风险干扰另一个被删除(引用本身是不同的)) .

所以我想知道不可变类型的实例是如何固有的线程安全的?

不可变类型的实例本质上是线程安全的

为什么不可变类型的实例本质上是线程安全的?

因为一个string类型的实例不能在多个线程间被改变。这实际上意味着一个线程更改string不会导致相同的string在另一个线程中被更改,因为在发生突变的地方分配了一个新的string

一般来说,当您创建一个对象,然后只观察它时,一切都会变得更容易。一旦需要修改它,就会创建一个新的本地副本。

维基百科:

不可变对象在多线程应用程序中很有用。多个线程可以对由不可变对象表示的数据进行操作无需担心数据被其他线程更改。不可变的因此,对象被认为是线程安全的,而不是可变的对象。

@xanatos(和wikipedia)指出不可变并不总是线程安全的。我们喜欢建立这种关联,因为我们说"任何具有持久不变状态的类型在线程边界上都是安全的",但情况可能并不总是如此。假设一个类型从"外部"是不可变的,但在内部需要修改它的状态,这种方式在多线程并行执行时可能不安全,并且可能导致不确定的行为。这意味着虽然不可变,但它不是线程安全的。

总结一下,immutable !=线程安全。但是,如果做得好,不可变性确实会使您更接近于能够正确地执行多线程工作。

简短的回答:

因为您只在一个线程中写入数据,并且总是在多个线程中写入之后读取它。因为没有读/写冲突,所以它是线程安全的。

长答案:

string本质上是一个指向内存缓冲区的指针。基本上,所发生的事情是你创建一个缓冲区,用字符填充它,然后将指针公开给外部世界。

请注意,在字符串对象本身构造之前,不能访问字符串的内容,这强制执行了"写数据",然后"暴露指针"的顺序。如果你用另一种方法(我想这在理论上是可能的),可能会出现问题。

如果另一个线程(假设:CPU)读取指针,它是CPU的"新指针",因此需要CPU去"真实"内存,然后读取数据。如果它从缓存中取出指针内容,那就有问题了。

最后一块拼图与内存管理有关:我们必须知道它是一个"新"指针。在。net中,我们知道这种情况:堆上的内存基本上不会被重用,直到发生GC。然后,垃圾收集器进行标记、清扫和压缩。

现在,你可能会说"压缩"阶段重用指针,因此改变了指针的内容。虽然这是真的,但GC还必须停止线程并强制使用一个满内存围栏,简单地说,这将刷新CPU缓存。在此之后,所有的内存访问都是有保证的,这确保了在GC阶段完成后始终必须访问内存。

如您所见,如果不直接从内存中读取数据(写入数据的方式),则无法读取数据。由于它是不可变的,所以在最终收集它之前,所有线程的内容都保持不变。因此,它是线程安全的。


我在这里看到了一些关于不可变的讨论,这表明你可以改变内部状态。当然,当您开始更改内容时,您可能会引入读/写冲突。

我在这里使用的定义是在创建后保持内容不变。即:写一次,读多次,在暴露指针后不改变(任何)状态。

多线程代码中最大的问题之一是两个线程同时访问同一个内存单元,并且其中至少一个线程修改这个内存单元。

如果没有线程可以修改内存单元,则问题不再存在。

因为不可变变量是不可修改的,所以它可以在多个线程中使用,而不需要任何进一步的措施(例如锁)。