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

时间:2015-06-09 06:49:52

标签: c# thread-safety immutability

我搜索为什么.NET String是不可变的?得到this回答:

  

不可变类型的实例本质上是线程安全的,因为没有   线程可以修改它,线程以某种方式修改它的风险   与另一个人的干涉被删除(参考本身是不同的   物质)。

所以我想知道不可变类型的实例本身就是线程安全的吗?

3 个答案:

答案 0 :(得分:4)

  

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

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

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

Wikipedia

  

不可变对象在多线程应用程序中很有用。   多个线程可以对不可变对象表示的数据起作用   无需担心其他线程正在更改数据。不可变   因此,被认为比mutable更加线程安全   对象。

@xanatos(和维基百科)指出,immutable并不总是线程安全的。我们喜欢进行相关性,因为我们说“任何具有持久不变状态的类型在线程边界上都是安全的”,但可能并非总是如此。假设类型从“外部”是不可变的,但是内部将需要以从多个线程并行完成时可能不安全的方式修改它的状态,并且可能导致未确定的行为。这意味着虽然不可变,但它不是线程安全的。

总结一下,不可变!=线程安全。但是,当完成正确时,不变性确实会使您更接近一步,以便能够正确地执行多线程工作。

答案 1 :(得分:1)

答案简短:

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

答案很长:

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

请注意,在构造字符串对象之前,您无法访问字符串的内容,这会强制执行“写入数据”的排序,然后“公开指针”。如果你反过来这样做(我想这在理论上是可行的),问题可能会出现。

如果另一个线程(比方说:CPU)读取指针,则它是CPU的“新指针”,因此需要CPU转到“真实”存储器然后读取数据。如果从缓存中获取指针内容,我们就会遇到问题。

最后一块拼图与内存管理有关:我们必须知道它是一个'新'指针。在.NET中,我们知道情况就是如此:在GC发生之前,堆上的内存基本上不会被重用。然后垃圾收集器进行标记,扫描和压缩。

现在,您可能会争辩说'compact'阶段会重用指针,因此会更改指针的内容。虽然这是事实,但GC还必须停止线程并强制使用完整的内存栏,简单来说就是刷新CPU缓存。之后,所有内存访问都得到保证,这可确保您在GC阶段完成后始终必须进入内存。

正如您所看到的,没有办法通过不直接从内存中读取数据来读取数据(编写方式)。由于它是不可变的,因此在最终收集之前,所有线程的内容保持不变。因此,它是线程安全的。

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

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

答案 2 :(得分:0)

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

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

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