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

How Instances of immutable types are inherently thread-safe

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

Instances of immutable types are inherently thread-safe, since no thread can modify it, the risk of a thread modifying it in a way that interfers with another is removed (the reference itself is a different matter).

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

Why Instances of immutable types are inherently thread-safe?

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

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

Wikipedia:

Immutable objects can be useful in multi-threaded applications. Multiple threads can act on data represented by immutable objects without concern of the data being changed by other threads. Immutable objects are therefore considered to be more thread-safe than mutable objects.

@xanatos(和维基百科)指出不可变并不总是线程安全的。我们喜欢建立这种相关性,因为我们说 "any type which has persistent non-changing state is safe across thread boundaries",但情况可能并非总是如此。假设一个类型在 "outside" 中是不可变的,但在内部需要修改它的状态,这种方式在从多个线程并行完成时可能不安全,并且可能导致不确定的行为。这意味着虽然不可变,但它不是线程安全的。

总而言之,不可变!= 线程安全。但是不变性确实会让你更进一步,如果做得好,能够正确地进行多线程工作。

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

如果none个线程可以修改一个内存单元,问题就不存在了。

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

简答:

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

长答案:

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

请注意,在构造字符串对象本身之前,您无法访问字符串的内容,这会强制执行 'write data',然后 'expose pointer' 的顺序。如果你反过来做(我想这在理论上是可能的),可能会出现问题。

如果另一个线程(比方说:CPU)读取指针,它是 CPU 的 'new pointer',因此需要 CPU 去'real' 内存,然后读取数据。如果它从缓存中获取指针内容,我们就会遇到问题。

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

现在,您可能会争辩说 'compact' 阶段重用了指针,因此改变了指针的内容。虽然这是事实,但 GC 还必须停止线程并强制使用完整的内存栅栏,简单来说,就是刷新 CPU 缓存。之后,所有的内存访问都是有保证的,保证了GC阶段结束后总要去内存。

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


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

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