【问题标题】:How can an immutable data structure NOT be thread safe?不可变的数据结构怎么可能不是线程安全的?
【发布时间】:2020-12-07 16:03:48
【问题描述】:

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

不可变数据结构的线程安全就是确保所有操作中数据的使用在逻辑上是一致的,但代价是您正在查看可能已过时的不可变快照。

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

Lippert 在这里是什么意思?

【问题讨论】:

  • 印刷报纸是不可变的。印刷报纸会过时吗?
  • 公平点,但在文章中,Lippert 专门讨论了线程安全。他可能会说“不可变的数据结构是不可变的,因此它们是线程安全的”,但他却说“确保所有操作中数据的使用在逻辑上是一致的”,这听起来好像还有更多需要注意的地方关于那个简单的不变性。
  • Eric L. 不只是说“不可变”,他说“不可变快照”。这意味着有一些数据结构可以随时更改,并且不可变的“快照”是该数据结构状态的冻结副本,就像在最近的某个时刻一样。当一个线程查看“快照”并根据其内容做出决定时,其他线程可能正在更新“实时”副本。
  • 如果原始数据结构是不可变的,那么就不需要“快照”。您引用的那篇文章清楚地说,“快照”。
  • @NathanHughes:谢谢分享;我以前没见过。嵌入在不断变化的世界中的不可变数据结构上的纯函数正是 IDE 所处的情况,我们在设计异步工作流时考虑了该文档中提出的许多想法。但要让它们更实用,还有很多工作要做。

标签: multithreading thread-safety immutability


【解决方案1】:

Lippert 在这里是什么意思?

我同意我写那个特定部分的方式并不像它可能的那样清晰。

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

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

正是它们不会改变的事实使它们可能过时了。考虑 IDE 中的一个常见场景:

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

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

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

如果当我们在后台线程上解决所有这些问题时,你点击“退格”然后“W”会发生什么?

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

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

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

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

【讨论】:

  • 哇。你显然在处理一个比我更棘手的问题。我只是想处理一个非常罕见的错误,其中一个线程修改了一个正在被另一个线程枚举的列表。这种情况很少见,只是因为它发生的时间窗口非常小,但它确实会造成相当大的损害。修复只是创建一个 ConcurrentList,它在内部使用锁定,当您调用 GetEnumerator 时,它会克隆列表并返回副本的枚举器,因此是“不可变快照”。您的文章确实帮助我了解了如何思考整个情况。
【解决方案2】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多