不可变数据结构怎么可能不是线程安全的?

How can an immutable data structure NOT be thread safe?

在名为 What is this thing you call "thread safe"? 的 post 中,Eric Lippert 说:

Thread safety of immutable data structures is all about ensuring that use of the data across all operations is logically consistent, at the expense of the fact that you're looking at an immutable snapshot that might be out-of-date.

我认为不可变数据结构的全部意义在于它们不会改变,因此不会过时,因此它们本质上是线程安全的

Lippert 在这里是什么意思?

他的意思是您可能正在查看与其他人不同的快照。考虑 cons lists 是如何工作的:在将另一个元素添加到列表的头部之后,实际上有两个列表(快照)。两者都是不可变的,但并不相同。

What does Lippert mean here?

我承认我写那个特定位的方式不够清晰。

早在 2009 年,我们就在为 Roslyn 设计数据结构——“C# 和 VB 编译器即服务”——因此正在考虑如何在 IDE 中进行分析,一个代码几乎从不正确的世界——如果代码是正确的,你为什么要编辑它? -- 并且它可以在您键入时每秒更改几次。

I thought the whole point of immutable data structures is that they do not change and therefore cannot be out of date, and that therefore they are intrinsically thread-safe.

正是因为它们没有改变,所以它们可能已经过时了。考虑 IDE 中的一个常见场景:

using System;
class P
{
  static void Main()
  {
    Console.E
  }
}

我们有一个不可变的数据结构,它代表 在你输入“E”之前 此刻的世界,我们有一个不可变的数据结构代表 编辑 您刚刚制作了 -- 敲击字母 E -- 现在发生了一大堆事情。

词法分析器,知道之前的词法状态是不可变的并且匹配“E”之前的世界重新词法 就在 E 周围,而不是重新词法化整个令牌流。类似地,解析器计算出用于此编辑的新(格式错误!)解析树是什么。这创建了一个新的不可变解析树,它是对旧的不可变解析树的编辑,然后真正的乐趣开始了。语义分析器试图找出 Console 的含义,然后找出 E 可能的含义,以便它可以执行以 System.Console 的成员为中心的 IntelliSense 下拉列表,这些成员以 [=12] 开头=],如果有的话。 (而且我们还启动了一个错误报告工作流程,因为现在程序中存在许多语义和句法错误。)

现在如果当我们在后台线程上解决所有这些问题时,您点击“退格键”然后点击“W”会怎样?

所有仍在进行中的分析将正确,但对Console.E和不是 Console.W。分析已经过时了。它属于一个不再相关的世界,我们必须重新开始分析退格和W。

简而言之,在另一个线程上分析不可变数据结构是完全 安全的 ,但是 UI 线程上可能会继续发生一些事情,使它无效工作;这是您为工作线程处理不可变数据而付出的代价之一。

请记住,这些失效发生的速度非常快;我们为 re-lex、re-parse 和 IntelliSense 更新预算了 30 毫秒,因为快速打字员每秒可以完成十次以上的击键;拥有一个重新使用过去词法和解析的不可变状态的词法分析器和解析器是该策略的关键部分,但是您必须计划一个失效,它会尽快丢弃您当前的分析。

顺便说一句,我们需要发明的机制来有效地跟踪这些失效本身非常有趣,并且导致了对基于取消的工作流的一些见解——但这是另一天的主题。