原来的问题
我是 STM 的新手。我想在 Haskell 中做的一件事涉及一大块数据,以及大量轻量级线程读取和写入所述大块数据的一小部分。读取和写入的位置基本上可以认为是随机的和小的。STM 似乎很适合这个,但我对如何解决这个问题有一些疑问。我看到了几种可能的方法,每种方法都有一些缺点,有些看起来很愚蠢。对这些或其他替代方案的一些评论将不胜感激。
为简单起见,我们假设大数据是 a Data.Vector a
,其中元素本身很小。
整条数据为一个
TVar (Vector a)
。我想这会导致大量数据的大量复制,因为 STM 会认为每个单独的写入都可能影响整个共享数据。当然,STM 识别出读取和写入非常本地化,并且不需要跨大数据的一致性的地方没有魔法?大量的
TVar a
s,基本上每个元素一个,提供完全本地化的 STM,但基本上复制了整个Vector a
. 这似乎很愚蠢。通过对数据进行分段,在 1 和 2 之间进行折衷,以便我有合理数量的
TVar (Vector a)
s 对应于数据的子向量。我觉得这个解决方案过于依赖启发式方法,比如分段应该有多大。消息传递。不是每个工作人员都使用 STM 读取和写入数据,而是每个工作人员都通过一些STM 机制(例如
TChan
. 一个特殊的线程接收这些消息,通过另一个线程传递请求的数据TChan
,或者获取接收到的数据并将其写入共享数据结构。这个解决方案似乎没有困扰解决方案 1-3 的问题,但在我看来,它基本上放弃了使用 STM 的细节来保持数据一致。相反,它只是消息传递。当然,消息传递部分是用 STM 实现的,但我真正的问题是通过消息传递解决的。STM 看起来太棒了,消息传递太……嗯。
我是否正确地考虑了这一点?有人有任何提示或其他建议吗?
请记住,我没有使用 STM 的经验,也没有尝试过上述解决方案。我会从扶手椅上站起来,但有时在尝试任何事情之前考虑这些事情可能会很好。
附录:第五种方法来自 Nathan Howell,并使用TArray
. 这听起来像我想要的,但文档说:
它目前以 Array ix (TVar e) 的形式实现,但将来可能会被更高效的实现所取代(但接口将保持不变)。
我认为这意味着这TArray
只是我穿着更好衣服的方法 2。暗示“更有效”实现的文档很有趣,因为它暗示实际上存在更好的方法。
跟进 Vagif Verdi 的回答
Vagif Verdi 的回答很有趣,所以我做了一个小实验来尝试一下。我现在没有时间精简代码,因此对此感兴趣的人将不得不忍受我的代码,而不仅仅是包含基本要素。我决定使用 10^8Int
的可变向量作为“大共享数据”,并让多个读取器/写入器对应于网络套接字上的线程。
请注意,代码甚至不读取或写入共享数据块。它就在那里,每个线程都有一个TVar
。
那么会发生什么?我运行该程序,它立即占用了大约 780 MB 的 RAM,这是意料之中的(这大约是 10^8 Int
s 所需要的)。现在,如果我使用 netcat 连接几个客户端并编写一些文本,程序只是应该打印出来,甚至不写入共享数据,那么进程的 CPU 使用率会在一秒以上的时间内飙升至 100% !在显示文本之前有明显的延迟。从好的方面来说,内存使用量保持不变(根据 Vagif Verdi 的回答)。如果我删除向量和TVar
,即取出所有 STM 和共享数据,一切都非常快速且响应迅速,并且每当客户端写入内容时都没有明显的 CPU 使用率。
因此,虽然很高兴看到共享数据实际上没有重复(尽管我想我应该实际写入共享数据以完全验证这一点),但维护一致状态会带来非常严重的性能损失。对我来说,我的问题仍然存在:如何在保持 STM 优点的同时正确解决这个问题?
感谢 Vagif Verdi 提出了一些非常有趣的观点。