问题 Haskell线程通信模式场景


你有两个线程,a和b。线程a处于永久循环中,侦听阻塞套接字1.线程b也处于永久循环中,侦听阻塞套接字2.套接字1和套接字2都可以在任意时间返回数据,因此线程a可能永远在睡觉等待数据,而线程b不断从套接字获取数据并继续处理。这就是背景。

现在假设他们需要共享字典。当线程a获取一些数据(如果有的话)时,它会在一些处理之后将一个键值对添加到字典中,然后继续等待更多数据。当线程b从其套接字接收数据时,它首先查询字典,以查看在继续处理之前是否存在与其已接收的数据相关的信息。字典没有删除,只有插入和查询(如果这在最终解决方案中有所不同,我会感兴趣)。

在像python或c这样的标准命令式语言中,通过使字典在两个作用域中都可用并且仅在线程获得锁定后查询它,这很容易做到,因此线程B总是看到最多(几乎是)最新的字典。

在Haskell中,我似乎正在努力想出这个模式的良好实现。 MVars,一次只能有一个项目因此不能将一个项目放入字典中,因为可能会发生新的更新,并且在线程b从MVar中获取它之前它将无法推送新的字典。另一方面,如果线程b使用MVar发送就绪信号“ok!”线程a,可能是线程a在其读取套接字上休眠的情况,因此在读取套接字解除阻塞之前它将无法发回数据!还有频道,但这看起来很混乱,因为我不得不继续发送新的词典,而线程B将丢弃除最后一个之外的所有字典。

可行的替代解决方案是简单地沿着通道发送更新,并让线程B为自己构建字典。但是我想知道是否有更好的替代解决方案。

感谢您抽出宝贵时间阅读这个非常长的问题!


9601
2018-01-02 21:06


起源

我没有看到问题 MVar秒。如果A被唤醒并获得新数据,它会尝试 takeMVar 来自的词典 MVar。成功后,更新字典, putMVar 字典,回到睡眠状态。当B获取数据时,请尝试 takeMVar,抬头放回去。这会破坏哪里? - Daniel Fischer
谢谢!拍摄对不起,这正是我所寻找的,没关系。我还是个乞丐。我曾经认为如果放入它就无法取出,只有另一个线程可以。在我脑后的愚蠢假设!我现在还不确定如何关闭这个问题? - Anil Vaitla
我可以把它作为一个答案,你可以接受将问题标记为已解决,或者你可以删除问题,如果你愿意(我不知道如何,某个地方应该有一个删除链接,我相信)。 - Daniel Fischer
保留问题的+1 - ehird
似乎@ehird不是唯一保留它的人。下面的答案,我当然不会反对从upvotes或接受代表;) - Daniel Fischer


答案:


你可以使用 MVar 以下列方式:

  • 当线程A获取新数据时,它会尝试获取字典 takeMVar。当成功时,它会更新字典并将其重新放入 MVar
  • 当线程B获取数据时,它会尝试获取字典 takeMVar  - 在上述场景中,A很少能获得平均成功相当快的数据。然后它执行查找并将字典放回去。

正如哈马尔指出的那样,最好不要直接使用 takeMVar 和 putMVar 而是将它们包裹起来 modifyMVar_ RESP。 modifyMVar 不要离开 MVar 如果一个线程在使用字典时出现异常,则为空。

在线程A中,类似于

modifyMVar_ mvar (\dict -> putMVar mvar (insert newStuff dict))

在线程B中你需要的只是一个简单的 readMVar (再次感谢@hammar指出这一点)。


10
2018-01-02 21:27



对于线程B,我认为是 readMVar 因为字典没有被更新,所以足够(并且可能更有效)。 - hammar
大概。它不太可能被看跌和看跌之间的异常击中,但并非不可能。然后 MVar 可能最终是空的。另一方面,异常很可能无论如何都要杀死整个过程。 - Daniel Fischer
实施 readMVar 使用 mask_所以我不认为是例外 能够 在take和put之间发生。 - hammar
好点子。将编辑,谢谢。 - Daniel Fischer